blob: f9bcd703ebc21352346baf60debfc4631a7b8b09 [file] [log] [blame]
Mike Frysinger2de7f042012-07-10 04:45:03 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
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
Mike Frysinger12d055b2022-06-23 12:26:47 -040015import ast
Josh Triplett472a4182013-03-08 11:48:57 -080016import glob
Chris McDonaldb55b7032021-06-17 16:41:32 -060017import logging
Brian Harringb938c782012-02-29 15:14:38 -080018import os
Mike Frysinger23b5cf52021-06-16 23:18:00 -040019from pathlib import Path
Josh Triplett472a4182013-03-08 11:48:57 -080020import pwd
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060021import random
Brian Norrisd37e2f72016-08-22 16:09:24 -070022import re
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -070023import resource
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040024import shlex
Sergey Frolov1cb46ec2020-12-09 21:46:16 -070025import subprocess
David James56e6c2c2012-10-24 23:54:41 -070026import sys
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040027from typing import List
Mike Frysingere852b072021-05-21 12:39:03 -040028import urllib.parse
Brian Harringb938c782012-02-29 15:14:38 -080029
Chris McDonaldb55b7032021-06-17 16:41:32 -060030from chromite.cbuildbot import cbuildbot_alerts
Brian Harringb6cf9142012-09-01 20:43:17 -070031from chromite.lib import commandline
Chris McDonaldb55b7032021-06-17 16:41:32 -060032from chromite.lib import constants
Brian Harringb938c782012-02-29 15:14:38 -080033from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060034from chromite.lib import cros_sdk_lib
Brian Harringb938c782012-02-29 15:14:38 -080035from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080036from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070037from chromite.lib import osutils
Yong Hong84ba9172018-02-07 01:37:42 +080038from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040039from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080040from chromite.lib import retry_util
Michael Mortensenbf296fb2020-06-18 18:21:54 -060041from chromite.lib import timeout_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050042from chromite.lib import toolchain
Mike Frysingere652ba12019-09-08 00:57:43 -040043from chromite.utils import key_value_store
44
Brian Harringb938c782012-02-29 15:14:38 -080045
Mike Frysingerf744d032022-05-07 20:38:39 -040046# Which compression algos the SDK tarball uses. We've used xz since 2012.
Alex Klein1699fab2022-09-08 08:46:06 -060047COMPRESSION_PREFERENCE = ("xz",)
Zdenek Behanfd0efe42012-04-13 04:36:40 +020048
Brian Harringb938c782012-02-29 15:14:38 -080049# TODO(zbehan): Remove the dependency on these, reimplement them in python
Manoj Guptab12f7302019-06-03 16:40:14 -070050ENTER_CHROOT = [
Alex Klein1699fab2022-09-08 08:46:06 -060051 os.path.join(constants.SOURCE_ROOT, "src/scripts/sdk_lib/enter_chroot.sh")
Manoj Guptab12f7302019-06-03 16:40:14 -070052]
Brian Harringb938c782012-02-29 15:14:38 -080053
Josh Triplett472a4182013-03-08 11:48:57 -080054# Proxy simulator configuration.
Alex Klein1699fab2022-09-08 08:46:06 -060055PROXY_HOST_IP = "192.168.240.1"
Josh Triplett472a4182013-03-08 11:48:57 -080056PROXY_PORT = 8080
Alex Klein1699fab2022-09-08 08:46:06 -060057PROXY_GUEST_IP = "192.168.240.2"
Josh Triplett472a4182013-03-08 11:48:57 -080058PROXY_NETMASK = 30
Alex Klein1699fab2022-09-08 08:46:06 -060059PROXY_VETH_PREFIX = "veth"
Josh Triplett472a4182013-03-08 11:48:57 -080060PROXY_CONNECT_PORTS = (80, 443, 9418)
Alex Klein1699fab2022-09-08 08:46:06 -060061PROXY_APACHE_FALLBACK_USERS = ("www-data", "apache", "nobody")
62PROXY_APACHE_MPMS = ("event", "worker", "prefork")
63PROXY_APACHE_FALLBACK_PATH = ":".join(
64 "/usr/lib/apache2/mpm-%s" % mpm for mpm in PROXY_APACHE_MPMS
65)
66PROXY_APACHE_MODULE_GLOBS = ("/usr/lib*/apache2/modules", "/usr/lib*/apache2")
Josh Triplett472a4182013-03-08 11:48:57 -080067
Josh Triplett9a495f62013-03-15 18:06:55 -070068# We need these tools to run. Very common tools (tar,..) are omitted.
Alex Klein1699fab2022-09-08 08:46:06 -060069NEEDED_TOOLS = ("curl", "xz")
Brian Harringb938c782012-02-29 15:14:38 -080070
Josh Triplett472a4182013-03-08 11:48:57 -080071# Tools needed for --proxy-sim only.
Alex Klein1699fab2022-09-08 08:46:06 -060072PROXY_NEEDED_TOOLS = ("ip",)
Brian Harringb938c782012-02-29 15:14:38 -080073
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060074# Tools needed when use_image is true (the default).
Alex Klein1699fab2022-09-08 08:46:06 -060075IMAGE_NEEDED_TOOLS = (
76 "losetup",
77 "lvchange",
78 "lvcreate",
79 "lvs",
80 "mke2fs",
81 "pvscan",
82 "thin_check",
83 "vgchange",
84 "vgcreate",
85 "vgs",
86)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060087
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070088# As space is used inside the chroot, the empty space in chroot.img is
89# allocated. Deleting files inside the chroot doesn't automatically return the
90# used space to the OS. Over time, this tends to make the sparse chroot.img
91# less sparse even if the chroot contents don't currently need much space. We
92# can recover most of this unused space with fstrim, but that takes too much
93# time to run it every time. Instead, check the used space against the image
94# size after mounting the chroot and only call fstrim if it looks like we could
95# recover at least this many GiB.
96MAX_UNUSED_IMAGE_GBS = 20
97
Mike Frysingercc838832014-05-24 13:10:30 -040098
Brian Harring1790ac42012-09-23 08:53:33 -070099def GetArchStageTarballs(version):
Alex Klein1699fab2022-09-08 08:46:06 -0600100 """Returns the URL for a given arch/version"""
101 extension = {"xz": "tar.xz"}
102 return [
103 toolchain.GetSdkURL(
104 suburl="cros-sdk-%s.%s" % (version, extension[compressor])
105 )
106 for compressor in COMPRESSION_PREFERENCE
107 ]
Brian Harring1790ac42012-09-23 08:53:33 -0700108
109
Mike Frysingerf744d032022-05-07 20:38:39 -0400110def FetchRemoteTarballs(storage_dir, urls):
Alex Klein1699fab2022-09-08 08:46:06 -0600111 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 Args:
114 storage_dir: Path where to save the tarball.
115 urls: List of URLs to try to download. Download will stop on first success.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200116
Alex Klein1699fab2022-09-08 08:46:06 -0600117 Returns:
118 Full path to the downloaded file.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700119
Alex Klein1699fab2022-09-08 08:46:06 -0600120 Raises:
121 ValueError: None of the URLs worked.
122 """
123 # Note we track content length ourselves since certain versions of curl
124 # fail if asked to resume a complete file.
125 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
126 status_re = re.compile(rb"^HTTP/[0-9]+(\.[0-9]+)? 200")
127 # pylint: disable=undefined-loop-variable
128 for url in urls:
129 logging.notice("Downloading tarball %s ...", urls[0].rsplit("/", 1)[-1])
130 parsed = urllib.parse.urlparse(url)
131 tarball_name = os.path.basename(parsed.path)
132 if parsed.scheme in ("", "file"):
133 if os.path.exists(parsed.path):
134 return parsed.path
135 continue
136 content_length = 0
137 logging.debug("Attempting download from %s", url)
138 result = retry_util.RunCurl(
139 ["-I", url],
140 print_cmd=False,
141 debug_level=logging.NOTICE,
142 capture_output=True,
143 )
144 successful = False
145 for header in result.stdout.splitlines():
146 # We must walk the output to find the 200 code for use cases where
147 # a proxy is involved and may have pushed down the actual header.
148 if status_re.match(header):
149 successful = True
150 elif header.lower().startswith(b"content-length:"):
151 content_length = int(header.split(b":", 1)[-1].strip())
152 if successful:
153 break
Brian Harring1790ac42012-09-23 08:53:33 -0700154 if successful:
Alex Klein1699fab2022-09-08 08:46:06 -0600155 break
156 else:
157 raise ValueError("No valid URLs found!")
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200158
Alex Klein1699fab2022-09-08 08:46:06 -0600159 tarball_dest = os.path.join(storage_dir, tarball_name)
160 current_size = 0
161 if os.path.exists(tarball_dest):
162 current_size = os.path.getsize(tarball_dest)
163 if current_size > content_length:
164 osutils.SafeUnlink(tarball_dest)
165 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 if current_size < content_length:
168 retry_util.RunCurl(
169 [
170 "--fail",
171 "-L",
172 "-y",
173 "30",
174 "-C",
175 "-",
176 "--output",
177 tarball_dest,
178 url,
179 ],
180 print_cmd=False,
181 debug_level=logging.NOTICE,
182 )
Brian Harringb938c782012-02-29 15:14:38 -0800183
Alex Klein1699fab2022-09-08 08:46:06 -0600184 # Cleanup old tarballs now since we've successfull fetched; only cleanup
185 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
186 # because we might have partial overlap between known prefixes.
187 for p in Path(storage_dir).glob("cros-sdk-*"):
188 if p.name == tarball_name:
189 continue
190 logging.info("Cleaning up old tarball: %s", p)
191 osutils.SafeUnlink(p)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 return tarball_dest
Brian Harringb938c782012-02-29 15:14:38 -0800194
195
Alex Klein1699fab2022-09-08 08:46:06 -0600196def EnterChroot(
197 chroot_path,
198 cache_dir,
199 chrome_root,
200 chrome_root_mount,
201 goma_dir,
202 goma_client_json,
203 reclient_dir,
204 reproxy_cfg_file,
205 working_dir,
206 additional_args,
207):
208 """Enters an existing SDK chroot"""
209 st = os.statvfs(os.path.join(chroot_path, "usr", "bin", "sudo"))
210 if st.f_flag & os.ST_NOSUID:
211 cros_build_lib.Die("chroot cannot be in a nosuid mount")
Mike Frysingere5456972013-06-13 00:07:23 -0400212
Alex Klein1699fab2022-09-08 08:46:06 -0600213 cmd = ENTER_CHROOT + ["--chroot", chroot_path, "--cache_dir", cache_dir]
214 if chrome_root:
215 cmd.extend(["--chrome_root", chrome_root])
216 if chrome_root_mount:
217 cmd.extend(["--chrome_root_mount", chrome_root_mount])
218 if goma_dir:
219 cmd.extend(["--goma_dir", goma_dir])
220 if goma_client_json:
221 cmd.extend(["--goma_client_json", goma_client_json])
222 if reclient_dir:
223 cmd.extend(["--reclient_dir", reclient_dir])
224 if reproxy_cfg_file:
225 cmd.extend(["--reproxy_cfg_file", reproxy_cfg_file])
226 if working_dir is not None:
227 cmd.extend(["--working_dir", working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700228
Alex Klein1699fab2022-09-08 08:46:06 -0600229 if additional_args:
230 cmd.append("--")
231 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700232
Alex Klein1699fab2022-09-08 08:46:06 -0600233 if "CHROMEOS_SUDO_RLIMITS" in os.environ:
234 _SetRlimits(os.environ.pop("CHROMEOS_SUDO_RLIMITS"))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400235
Alex Klein1699fab2022-09-08 08:46:06 -0600236 # Some systems set the soft limit too low. Bump it up to the hard limit.
237 # We don't override the hard limit because it's something the admins put
238 # in place and we want to respect such configs. http://b/234353695
239 soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
240 if soft != resource.RLIM_INFINITY and soft < 4096:
241 if soft < hard or hard == resource.RLIM_INFINITY:
242 resource.setrlimit(resource.RLIMIT_NPROC, (hard, hard))
Mike Frysingerebb23fc2022-06-06 21:17:39 -0400243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 # ThinLTO opens lots of files at the same time.
245 # Set rlimit and vm.max_map_count to accommodate this.
246 file_limit = 262144
247 soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
248 resource.setrlimit(
249 resource.RLIMIT_NOFILE, (max(soft, file_limit), max(hard, file_limit))
250 )
251 max_map_count = int(osutils.ReadFile("/proc/sys/vm/max_map_count"))
252 if max_map_count < file_limit:
253 logging.notice(
254 "Raising vm.max_map_count from %s to %s", max_map_count, file_limit
255 )
256 osutils.WriteFile("/proc/sys/vm/max_map_count", str(file_limit))
257 return cros_build_lib.dbg_run(cmd, check=False)
Brian Harringb938c782012-02-29 15:14:38 -0800258
259
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600260def _ImageFileForChroot(chroot):
Alex Klein1699fab2022-09-08 08:46:06 -0600261 """Find the image file that should be associated with |chroot|.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600262
Alex Klein1699fab2022-09-08 08:46:06 -0600263 This function does not check if the image exists; it simply returns the
264 filename that would be used.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 Args:
267 chroot: Path to the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600268
Alex Klein1699fab2022-09-08 08:46:06 -0600269 Returns:
270 Path to an image file that would be associated with chroot.
271 """
272 return chroot.rstrip("/") + ".img"
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600273
274
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600275def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600276 """Create a snapshot for the specified chroot VG/LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600277
Alex Klein1699fab2022-09-08 08:46:06 -0600278 Args:
279 snapshot_name: The name of the new snapshot.
280 chroot_vg: The name of the VG containing the origin LV.
281 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 Returns:
284 True if the snapshot was created, or False if a snapshot with the same
285 name already exists.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 Raises:
288 SystemExit: The lvcreate command failed.
289 """
290 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
291 logging.error(
292 "Cannot create snapshot %s: A volume with that name already "
293 "exists.",
294 snapshot_name,
295 )
296 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600297
Alex Klein1699fab2022-09-08 08:46:06 -0600298 cmd = [
299 "lvcreate",
300 "-s",
301 "--name",
302 snapshot_name,
303 "%s/%s" % (chroot_vg, chroot_lv),
304 ]
305 try:
306 logging.notice(
307 "Creating snapshot %s from %s in VG %s.",
308 snapshot_name,
309 chroot_lv,
310 chroot_vg,
311 )
312 cros_build_lib.dbg_run(cmd, capture_output=True)
313 return True
314 except cros_build_lib.RunCommandError as e:
315 cros_build_lib.Die("Creating snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600316
317
318def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600319 """Delete the named snapshot from the specified chroot VG.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 If the requested snapshot is not found, nothing happens. The main chroot LV
322 and internal thinpool LV cannot be deleted with this function.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600323
Alex Klein1699fab2022-09-08 08:46:06 -0600324 Args:
325 snapshot_name: The name of the snapshot to delete.
326 chroot_vg: The name of the VG containing the origin LV.
327 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 Raises:
330 SystemExit: The lvremove command failed.
331 """
332 if snapshot_name in (
333 cros_sdk_lib.CHROOT_LV_NAME,
334 cros_sdk_lib.CHROOT_THINPOOL_NAME,
335 ):
336 logging.error(
337 "Cannot remove LV %s as a snapshot. Use cros_sdk --delete "
338 "if you want to remove the whole chroot.",
339 snapshot_name,
340 )
341 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600342
Alex Klein1699fab2022-09-08 08:46:06 -0600343 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
344 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600345
Alex Klein1699fab2022-09-08 08:46:06 -0600346 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, snapshot_name)]
347 try:
348 logging.notice(
349 "Deleting snapshot %s in VG %s.", snapshot_name, chroot_vg
350 )
351 cros_build_lib.dbg_run(cmd, capture_output=True)
352 except cros_build_lib.RunCommandError as e:
353 cros_build_lib.Die("Deleting snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600354
355
356def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600357 """Restore the chroot to an existing snapshot.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 This is done by renaming the original |chroot_lv| LV to a temporary name,
360 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
361 now unused LV. If an error occurs, attempts to rename the original snapshot
362 back to |chroot_lv| to leave the chroot unchanged.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 The chroot must be unmounted before calling this function, and will be left
365 unmounted after this function returns.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 Args:
368 snapshot_name: The name of the snapshot to restore. This snapshot will no
369 longer be accessible at its original name after this function finishes.
370 chroot_vg: The VG containing the chroot LV and snapshot LV.
371 chroot_lv: The name of the original chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 Returns:
374 True if the chroot was restored to the requested snapshot, or False if
375 the snapshot wasn't found or isn't valid.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600376
Alex Klein1699fab2022-09-08 08:46:06 -0600377 Raises:
378 SystemExit: Any of the LVM commands failed.
379 """
380 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
381 if (
382 snapshot_name
383 in (cros_sdk_lib.CHROOT_LV_NAME, cros_sdk_lib.CHROOT_THINPOOL_NAME)
384 or snapshot_name not in valid_snapshots
385 ):
386 logging.error(
387 "Chroot cannot be restored to %s. Valid snapshots: %s",
388 snapshot_name,
389 ", ".join(valid_snapshots),
390 )
391 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600392
Alex Klein1699fab2022-09-08 08:46:06 -0600393 backup_chroot_name = "chroot-bak-%d" % random.randint(0, 1000)
394 cmd = ["lvrename", chroot_vg, chroot_lv, backup_chroot_name]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600395 try:
Alex Klein1699fab2022-09-08 08:46:06 -0600396 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500397 except cros_build_lib.RunCommandError as e:
Alex Klein1699fab2022-09-08 08:46:06 -0600398 cros_build_lib.Die("Restoring snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600399
Alex Klein1699fab2022-09-08 08:46:06 -0600400 cmd = ["lvrename", chroot_vg, snapshot_name, chroot_lv]
401 try:
402 cros_build_lib.dbg_run(cmd, capture_output=True)
403 except cros_build_lib.RunCommandError as e:
404 cmd = ["lvrename", chroot_vg, backup_chroot_name, chroot_lv]
405 try:
406 cros_build_lib.dbg_run(cmd, capture_output=True)
407 except cros_build_lib.RunCommandError as e:
408 cros_build_lib.Die(
409 "Failed to rename %s to chroot and failed to restore %s back to "
410 "chroot!\n%s",
411 snapshot_name,
412 backup_chroot_name,
413 e,
414 )
415 cros_build_lib.Die(
416 "Failed to rename %s to chroot! Original chroot LV has "
417 "been restored.\n%s",
418 snapshot_name,
419 e,
420 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600421
Alex Klein1699fab2022-09-08 08:46:06 -0600422 # Some versions of LVM set snapshots to be skipped at auto-activate time.
423 # Other versions don't have this flag at all. We run lvchange to try
424 # disabling auto-skip and activating the volume, but ignore errors. Versions
425 # that don't have the flag should be auto-activated.
426 chroot_lv_path = "%s/%s" % (chroot_vg, chroot_lv)
427 cmd = ["lvchange", "-kn", chroot_lv_path]
428 cros_build_lib.run(cmd, print_cmd=False, capture_output=True, check=False)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600429
Alex Klein1699fab2022-09-08 08:46:06 -0600430 # Activate the LV in case the lvchange above was needed. Activating an LV
431 # that is already active shouldn't do anything, so this is safe to run even if
432 # the -kn wasn't needed.
433 cmd = ["lvchange", "-ay", chroot_lv_path]
Mike Frysinger3e8de442020-02-14 16:46:28 -0500434 cros_build_lib.dbg_run(cmd, capture_output=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, backup_chroot_name)]
437 try:
438 cros_build_lib.dbg_run(cmd, capture_output=True)
439 except cros_build_lib.RunCommandError as e:
440 cros_build_lib.Die(
441 "Failed to remove backup LV %s/%s!\n%s",
442 chroot_vg,
443 backup_chroot_name,
444 e,
445 )
446
447 return True
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600448
449
450def ListChrootSnapshots(chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600451 """Return all snapshots in |chroot_vg| regardless of origin volume.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 Args:
454 chroot_vg: The name of the VG containing the chroot.
455 chroot_lv: The name of the chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 Returns:
458 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 Raises:
461 SystemExit: The lvs command failed.
462 """
463 if not chroot_vg or not chroot_lv:
464 return []
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 cmd = [
467 "lvs",
468 "-o",
469 "lv_name,pool_lv,lv_attr",
470 "-O",
471 "lv_name",
472 "--noheadings",
473 "--separator",
474 "\t",
475 chroot_vg,
476 ]
477 try:
478 result = cros_build_lib.run(
479 cmd, print_cmd=False, stdout=True, encoding="utf-8"
480 )
481 except cros_build_lib.RunCommandError:
482 raise SystemExit("Running %r failed!" % cmd)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600483
Alex Klein1699fab2022-09-08 08:46:06 -0600484 # Once the thin origin volume has been deleted, there's no way to tell a
485 # snapshot apart from any other volume. Since this VG is created and managed
486 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
487 # valid snapshots.
488 snapshots = []
489 snapshot_attrs = re.compile(r"^V.....t.{2,}") # Matches a thin volume.
490 for line in result.stdout.splitlines():
491 lv_name, pool_lv, lv_attr = line.lstrip().split("\t")
492 if (
493 lv_name == chroot_lv
494 or lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME
495 or pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME
496 or not snapshot_attrs.match(lv_attr)
497 ):
498 continue
499 snapshots.append(lv_name)
500 return snapshots
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600501
502
Mike Frysinger12d055b2022-06-23 12:26:47 -0400503# The rlimits we will lookup & pass down, in order.
504RLIMITS_TO_PASS = (
505 resource.RLIMIT_AS,
506 resource.RLIMIT_CORE,
507 resource.RLIMIT_CPU,
508 resource.RLIMIT_FSIZE,
509 resource.RLIMIT_MEMLOCK,
510 resource.RLIMIT_NICE,
511 resource.RLIMIT_NOFILE,
512 resource.RLIMIT_NPROC,
513 resource.RLIMIT_RSS,
514 resource.RLIMIT_STACK,
515)
516
517
518def _GetRlimits() -> str:
Alex Klein1699fab2022-09-08 08:46:06 -0600519 """Serialize current rlimits."""
520 return str(tuple(resource.getrlimit(x) for x in RLIMITS_TO_PASS))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400521
522
523def _SetRlimits(limits: str) -> None:
Alex Klein1699fab2022-09-08 08:46:06 -0600524 """Deserialize rlimits."""
525 for rlim, limit in zip(RLIMITS_TO_PASS, ast.literal_eval(limits)):
526 cur_limit = resource.getrlimit(rlim)
527 if cur_limit != limit:
528 # Turn the number into a symbolic name for logging.
529 name = "RLIMIT_???"
530 for name, num in resource.__dict__.items():
531 if name.startswith("RLIMIT_") and num == rlim:
532 break
533 logging.debug(
534 "Restoring user rlimit %s from %r to %r", name, cur_limit, limit
535 )
Mike Frysinger12d055b2022-06-23 12:26:47 -0400536
Alex Klein1699fab2022-09-08 08:46:06 -0600537 resource.setrlimit(rlim, limit)
Mike Frysinger12d055b2022-06-23 12:26:47 -0400538
539
David James56e6c2c2012-10-24 23:54:41 -0700540def _SudoCommand():
Alex Klein1699fab2022-09-08 08:46:06 -0600541 """Get the 'sudo' command, along with all needed environment variables."""
David James56e6c2c2012-10-24 23:54:41 -0700542
Alex Klein1699fab2022-09-08 08:46:06 -0600543 # Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
544 # scripts in the chroot know what variables to pass through.
545 cmd = ["sudo"]
546 for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
547 value = os.environ.get(key)
548 if value is not None:
549 cmd += ["%s=%s" % (key, value)]
David James56e6c2c2012-10-24 23:54:41 -0700550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 # We keep PATH not for the chroot but for the re-exec & for programs we might
552 # run before we chroot into the SDK. The process that enters the SDK itself
553 # will take care of initializing PATH to the right value then. But we can't
554 # override the system's default PATH for root as that will hide /sbin.
555 cmd += ["CHROMEOS_SUDO_PATH=%s" % os.environ.get("PATH", "")]
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400556
Alex Klein1699fab2022-09-08 08:46:06 -0600557 # Pass along current rlimit settings so we can restore them.
558 cmd += [f"CHROMEOS_SUDO_RLIMITS={_GetRlimits()}"]
Mike Frysinger12d055b2022-06-23 12:26:47 -0400559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 # Pass in the path to the depot_tools so that users can access them from
561 # within the chroot.
562 cmd += ["DEPOT_TOOLS=%s" % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500563
Alex Klein1699fab2022-09-08 08:46:06 -0600564 return cmd
David James56e6c2c2012-10-24 23:54:41 -0700565
566
Josh Triplett472a4182013-03-08 11:48:57 -0800567def _ReportMissing(missing):
Alex Klein1699fab2022-09-08 08:46:06 -0600568 """Report missing utilities, then exit.
Josh Triplett472a4182013-03-08 11:48:57 -0800569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 Args:
571 missing: List of missing utilities, as returned by
572 osutils.FindMissingBinaries. If non-empty, will not return.
573 """
Josh Triplett472a4182013-03-08 11:48:57 -0800574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 if missing:
576 raise SystemExit(
577 "The tool(s) %s were not found.\n"
578 "Please install the appropriate package in your host.\n"
579 "Example(ubuntu):\n"
580 " sudo apt-get install <packagename>" % ", ".join(missing)
581 )
Josh Triplett472a4182013-03-08 11:48:57 -0800582
583
584def _ProxySimSetup(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600585 """Set up proxy simulator, and return only in the child environment.
Josh Triplett472a4182013-03-08 11:48:57 -0800586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 TODO: Ideally, this should support multiple concurrent invocations of
588 cros_sdk --proxy-sim; currently, such invocations will conflict with each
589 other due to the veth device names and IP addresses. Either this code would
590 need to generate fresh, unused names for all of these before forking, or it
591 would need to support multiple concurrent cros_sdk invocations sharing one
592 proxy and allowing it to exit when unused (without counting on any local
593 service-management infrastructure on the host).
594 """
Josh Triplett472a4182013-03-08 11:48:57 -0800595
Alex Klein1699fab2022-09-08 08:46:06 -0600596 may_need_mpm = False
597 apache_bin = osutils.Which("apache2")
Josh Triplett472a4182013-03-08 11:48:57 -0800598 if apache_bin is None:
Alex Klein1699fab2022-09-08 08:46:06 -0600599 apache_bin = osutils.Which("apache2", PROXY_APACHE_FALLBACK_PATH)
600 if apache_bin is None:
601 _ReportMissing(("apache2",))
602 else:
603 may_need_mpm = True
Josh Triplett472a4182013-03-08 11:48:57 -0800604
Alex Klein1699fab2022-09-08 08:46:06 -0600605 # Module names and .so names included for ease of grepping.
606 apache_modules = [
607 ("proxy_module", "mod_proxy.so"),
608 ("proxy_connect_module", "mod_proxy_connect.so"),
609 ("proxy_http_module", "mod_proxy_http.so"),
610 ("proxy_ftp_module", "mod_proxy_ftp.so"),
611 ]
Josh Triplett472a4182013-03-08 11:48:57 -0800612
Alex Klein1699fab2022-09-08 08:46:06 -0600613 # Find the apache module directory, and make sure it has the modules we need.
614 module_dirs = {}
615 for g in PROXY_APACHE_MODULE_GLOBS:
616 for _, so in apache_modules:
617 for f in glob.glob(os.path.join(g, so)):
618 module_dirs.setdefault(os.path.dirname(f), []).append(so)
619 for apache_module_path, modules_found in module_dirs.items():
620 if len(modules_found) == len(apache_modules):
621 break
622 else:
623 # Appease cros lint, which doesn't understand that this else block will not
624 # fall through to the subsequent code which relies on apache_module_path.
625 apache_module_path = None
626 raise SystemExit(
627 "Could not find apache module path containing all required modules: %s"
628 % ", ".join(so for mod, so in apache_modules)
629 )
Josh Triplett472a4182013-03-08 11:48:57 -0800630
Alex Klein1699fab2022-09-08 08:46:06 -0600631 def check_add_module(name):
632 so = "mod_%s.so" % name
633 if os.access(os.path.join(apache_module_path, so), os.F_OK):
634 mod = "%s_module" % name
635 apache_modules.append((mod, so))
636 return True
637 return False
Josh Triplett472a4182013-03-08 11:48:57 -0800638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 check_add_module("authz_core")
640 if may_need_mpm:
641 for mpm in PROXY_APACHE_MPMS:
642 if check_add_module("mpm_%s" % mpm):
643 break
Josh Triplett472a4182013-03-08 11:48:57 -0800644
Alex Klein1699fab2022-09-08 08:46:06 -0600645 veth_host = "%s-host" % PROXY_VETH_PREFIX
646 veth_guest = "%s-guest" % PROXY_VETH_PREFIX
Josh Triplett472a4182013-03-08 11:48:57 -0800647
Alex Klein1699fab2022-09-08 08:46:06 -0600648 # Set up locks to sync the net namespace setup. We need the child to create
649 # the net ns first, and then have the parent assign the guest end of the veth
650 # interface to the child's new network namespace & bring up the proxy. Only
651 # then can the child move forward and rely on the network being up.
652 ns_create_lock = locking.PipeLock()
653 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800654
Alex Klein1699fab2022-09-08 08:46:06 -0600655 pid = os.fork()
656 if not pid:
657 # Create our new isolated net namespace.
658 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500659
Alex Klein1699fab2022-09-08 08:46:06 -0600660 # Signal the parent the ns is ready to be configured.
661 ns_create_lock.Post()
662 del ns_create_lock
663
664 # Wait for the parent to finish setting up the ns/proxy.
665 ns_setup_lock.Wait()
666 del ns_setup_lock
667
668 # Set up child side of the network.
669 commands = (
670 ("ip", "link", "set", "up", "lo"),
671 (
672 "ip",
673 "address",
674 "add",
675 "%s/%u" % (PROXY_GUEST_IP, PROXY_NETMASK),
676 "dev",
677 veth_guest,
678 ),
679 ("ip", "link", "set", veth_guest, "up"),
680 )
681 try:
682 for cmd in commands:
683 cros_build_lib.dbg_run(cmd)
684 except cros_build_lib.RunCommandError as e:
685 cros_build_lib.Die("Proxy setup failed!\n%s", e)
686
687 proxy_url = "http://%s:%u" % (PROXY_HOST_IP, PROXY_PORT)
688 for proto in ("http", "https", "ftp"):
689 os.environ[proto + "_proxy"] = proxy_url
690 for v in ("all_proxy", "RSYNC_PROXY", "no_proxy"):
691 os.environ.pop(v, None)
692 return
693
694 # Set up parent side of the network.
695 uid = int(os.environ.get("SUDO_UID", "0"))
696 gid = int(os.environ.get("SUDO_GID", "0"))
697 if uid == 0 or gid == 0:
698 for username in PROXY_APACHE_FALLBACK_USERS:
699 try:
700 pwnam = pwd.getpwnam(username)
701 uid, gid = pwnam.pw_uid, pwnam.pw_gid
702 break
703 except KeyError:
704 continue
705 if uid == 0 or gid == 0:
706 raise SystemExit("Could not find a non-root user to run Apache as")
707
708 chroot_parent, chroot_base = os.path.split(options.chroot)
709 pid_file = os.path.join(chroot_parent, ".%s-apache-proxy.pid" % chroot_base)
710 log_file = os.path.join(chroot_parent, ".%s-apache-proxy.log" % chroot_base)
711
712 # Wait for the child to create the net ns.
713 ns_create_lock.Wait()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500714 del ns_create_lock
715
Alex Klein1699fab2022-09-08 08:46:06 -0600716 apache_directives = [
717 "User #%u" % uid,
718 "Group #%u" % gid,
719 "PidFile %s" % pid_file,
720 "ErrorLog %s" % log_file,
721 "Listen %s:%u" % (PROXY_HOST_IP, PROXY_PORT),
722 "ServerName %s" % PROXY_HOST_IP,
723 "ProxyRequests On",
724 "AllowCONNECT %s" % " ".join(str(x) for x in PROXY_CONNECT_PORTS),
725 ] + [
726 "LoadModule %s %s" % (mod, os.path.join(apache_module_path, so))
727 for (mod, so) in apache_modules
728 ]
729 commands = (
730 (
731 "ip",
732 "link",
733 "add",
734 "name",
735 veth_host,
736 "type",
737 "veth",
738 "peer",
739 "name",
740 veth_guest,
741 ),
742 (
743 "ip",
744 "address",
745 "add",
746 "%s/%u" % (PROXY_HOST_IP, PROXY_NETMASK),
747 "dev",
748 veth_host,
749 ),
750 ("ip", "link", "set", veth_host, "up"),
751 (
752 [apache_bin, "-f", "/dev/null"]
753 + [arg for d in apache_directives for arg in ("-C", d)]
754 ),
755 ("ip", "link", "set", veth_guest, "netns", str(pid)),
756 )
757 cmd = None # Make cros lint happy.
758 try:
759 for cmd in commands:
760 cros_build_lib.dbg_run(cmd)
761 except cros_build_lib.RunCommandError as e:
762 # Clean up existing interfaces, if any.
763 cmd_cleanup = ("ip", "link", "del", veth_host)
764 try:
765 cros_build_lib.run(cmd_cleanup, print_cmd=False)
766 except cros_build_lib.RunCommandError:
767 logging.error("running %r failed", cmd_cleanup)
768 cros_build_lib.Die("Proxy network setup failed!\n%s", e)
769
770 # Signal the child that the net ns/proxy is fully configured now.
771 ns_setup_lock.Post()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500772 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800773
Alex Klein1699fab2022-09-08 08:46:06 -0600774 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800775
776
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400777def _BuildReExecCommand(argv, opts) -> List[str]:
Alex Klein1699fab2022-09-08 08:46:06 -0600778 """Generate new command for self-reexec."""
779 # Make sure to preserve the active Python executable in case the version
780 # we're running as is not the default one found via the (new) $PATH.
781 cmd = _SudoCommand() + ["--"]
782 if opts.strace:
783 cmd += ["strace"] + shlex.split(opts.strace_arguments) + ["--"]
784 return cmd + [sys.executable] + argv
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400785
786
787def _ReExecuteIfNeeded(argv, opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600788 """Re-execute cros_sdk as root.
David James56e6c2c2012-10-24 23:54:41 -0700789
Alex Klein1699fab2022-09-08 08:46:06 -0600790 Also unshare the mount namespace so as to ensure that processes outside
791 the chroot can't mess with our mounts.
792 """
793 if osutils.IsNonRootUser():
794 cmd = _BuildReExecCommand(argv, opts)
795 logging.debug(
796 "Reexecing self via sudo:\n%s", cros_build_lib.CmdToStr(cmd)
797 )
798 os.execvp(cmd[0], cmd)
David James56e6c2c2012-10-24 23:54:41 -0700799
800
Mike Frysinger34db8692013-11-11 14:54:08 -0500801def _CreateParser(sdk_latest_version, bootstrap_latest_version):
Alex Klein1699fab2022-09-08 08:46:06 -0600802 """Generate and return the parser with all the options."""
803 usage = (
804 "usage: %(prog)s [options] "
805 "[VAR1=val1 ... VAR2=val2] [--] [command [args]]"
806 )
807 parser = commandline.ArgumentParser(
808 usage=usage, description=__doc__, caching=True
809 )
Brian Harring218e13c2012-10-10 16:21:26 -0700810
Alex Klein1699fab2022-09-08 08:46:06 -0600811 # Global options.
812 default_chroot = os.path.join(
813 constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR
814 )
815 parser.add_argument(
816 "--chroot",
817 dest="chroot",
818 default=default_chroot,
819 type="path",
820 help=("SDK chroot dir name [%s]" % constants.DEFAULT_CHROOT_DIR),
821 )
822 parser.add_argument(
823 "--nouse-image",
824 dest="use_image",
825 action="store_false",
826 default=False,
827 help="Do not mount the chroot on a loopback image; "
828 "instead, create it directly in a directory.",
829 )
830 parser.add_argument(
831 "--use-image",
832 dest="use_image",
833 action="store_true",
834 default=False,
835 help="Mount the chroot on a loopback image "
836 "instead of creating it directly in a directory.",
837 )
Brian Harringb938c782012-02-29 15:14:38 -0800838
Alex Klein1699fab2022-09-08 08:46:06 -0600839 parser.add_argument(
840 "--chrome-root",
841 "--chrome_root",
842 type="path",
843 help="Mount this chrome root into the SDK chroot",
844 )
845 parser.add_argument(
846 "--chrome_root_mount",
847 type="path",
848 help="Mount chrome into this path inside SDK chroot",
849 )
850 parser.add_argument(
851 "--nousepkg",
852 action="store_true",
853 default=False,
854 help="Do not use binary packages when creating a chroot.",
855 )
856 parser.add_argument(
857 "-u",
858 "--url",
859 dest="sdk_url",
860 help="Use sdk tarball located at this url. Use file:// "
861 "for local files.",
862 )
863 parser.add_argument(
864 "--sdk-version",
865 help=(
866 "Use this sdk version. For prebuilt, current is %r"
867 ", for bootstrapping it is %r."
868 % (sdk_latest_version, bootstrap_latest_version)
869 ),
870 )
871 parser.add_argument(
872 "--goma_dir",
873 type="path",
874 help="Goma installed directory to mount into the chroot.",
875 )
876 parser.add_argument(
877 "--goma_client_json",
878 type="path",
879 help="Service account json file to use goma on bot. "
880 "Mounted into the chroot.",
881 )
882 parser.add_argument(
883 "--reclient-dir",
884 type="path",
885 help="Reclient installed directory to mount into the chroot.",
886 )
887 parser.add_argument(
888 "--reproxy-cfg-file",
889 type="path",
890 help="Config file for re-client's reproxy used for remoteexec.",
891 )
892 parser.add_argument(
893 "--skip-chroot-upgrade",
894 dest="chroot_upgrade",
895 action="store_false",
896 default=True,
897 help="Skip automatic SDK and toolchain upgrade when entering the chroot. "
898 "Never guaranteed to work, especially as ToT moves forward.",
899 )
Yong Hong84ba9172018-02-07 01:37:42 +0800900
Alex Klein1699fab2022-09-08 08:46:06 -0600901 # Use type=str instead of type='path' to prevent the given path from being
902 # transfered to absolute path automatically.
903 parser.add_argument(
904 "--working-dir",
905 type=str,
906 help="Run the command in specific working directory in "
907 "chroot. If the given directory is a relative "
908 "path, this program will transfer the path to "
909 "the corresponding one inside chroot.",
910 )
Yong Hong84ba9172018-02-07 01:37:42 +0800911
Alex Klein1699fab2022-09-08 08:46:06 -0600912 parser.add_argument("commands", nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500913
Alex Klein1699fab2022-09-08 08:46:06 -0600914 # Commands.
915 group = parser.add_argument_group("Commands")
Mike Frysinger79024a32021-04-05 03:28:35 -0400916 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600917 "--enter",
918 action="store_true",
919 default=False,
920 help="Enter the SDK chroot. Implies --create.",
921 )
Mike Frysinger79024a32021-04-05 03:28:35 -0400922 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600923 "--create",
924 action="store_true",
925 default=False,
926 help="Create the chroot only if it does not already exist. "
927 "Implies --download.",
928 )
929 group.add_argument(
930 "--bootstrap",
931 action="store_true",
932 default=False,
933 help="Build everything from scratch, including the sdk. "
934 "Use this only if you need to validate a change "
935 "that affects SDK creation itself (toolchain and "
936 "build are typically the only folk who need this). "
937 "Note this will quite heavily slow down the build. "
938 "This option implies --create --nousepkg.",
939 )
940 group.add_argument(
941 "-r",
942 "--replace",
943 action="store_true",
944 default=False,
945 help="Replace an existing SDK chroot. Basically an alias "
946 "for --delete --create.",
947 )
948 group.add_argument(
949 "--delete",
950 action="store_true",
951 default=False,
952 help="Delete the current SDK chroot if it exists.",
953 )
954 group.add_argument(
955 "--force",
956 action="store_true",
957 default=False,
958 help="Force unmount/delete of the current SDK chroot even if "
959 "obtaining the write lock fails.",
960 )
961 group.add_argument(
962 "--unmount",
963 action="store_true",
964 default=False,
965 help="Unmount and clean up devices associated with the "
966 "SDK chroot if it exists. This does not delete the "
967 "backing image file, so the same chroot can be later "
968 "re-mounted for reuse. To fully delete the chroot, use "
969 "--delete. This is primarily useful for working on "
970 "cros_sdk or the chroot setup; you should not need it "
971 "under normal circumstances.",
972 )
973 group.add_argument(
974 "--download",
975 action="store_true",
976 default=False,
977 help="Download the sdk.",
978 )
979 group.add_argument(
980 "--snapshot-create",
981 metavar="SNAPSHOT_NAME",
982 help="Create a snapshot of the chroot. Requires that the chroot was "
983 "created without the --nouse-image option.",
984 )
985 group.add_argument(
986 "--snapshot-restore",
987 metavar="SNAPSHOT_NAME",
988 help="Restore the chroot to a previously created snapshot.",
989 )
990 group.add_argument(
991 "--snapshot-delete",
992 metavar="SNAPSHOT_NAME",
993 help="Delete a previously created snapshot. Deleting a snapshot that "
994 "does not exist is not an error.",
995 )
996 group.add_argument(
997 "--snapshot-list",
998 action="store_true",
999 default=False,
1000 help="List existing snapshots of the chroot and exit.",
1001 )
1002 commands = group
Mike Frysinger80dfce92014-04-21 10:58:53 -04001003
Alex Klein1699fab2022-09-08 08:46:06 -06001004 # Namespace options.
1005 group = parser.add_argument_group("Namespaces")
1006 group.add_argument(
1007 "--proxy-sim",
1008 action="store_true",
1009 default=False,
1010 help="Simulate a restrictive network requiring an outbound" " proxy.",
1011 )
1012 for ns, default in (("pid", True), ("net", None)):
1013 group.add_argument(
1014 f"--ns-{ns}",
1015 default=default,
1016 action="store_true",
1017 help=f"Create a new {ns} namespace.",
1018 )
1019 group.add_argument(
1020 f"--no-ns-{ns}",
1021 dest=f"ns_{ns}",
1022 action="store_false",
1023 help=f"Do not create a new {ns} namespace.",
1024 )
Mike Frysinger5f5c70b2022-07-19 12:55:21 -04001025
Alex Klein1699fab2022-09-08 08:46:06 -06001026 # Debug options.
1027 group = parser.debug_group
1028 group.add_argument(
1029 "--strace",
1030 action="store_true",
1031 help="Run cros_sdk through strace after re-exec via sudo",
1032 )
1033 group.add_argument(
1034 "--strace-arguments",
1035 default="",
1036 help="Extra strace options (shell quoting permitted)",
1037 )
Mike Frysinger34db8692013-11-11 14:54:08 -05001038
Alex Klein1699fab2022-09-08 08:46:06 -06001039 # Internal options.
1040 group = parser.add_argument_group(
1041 "Internal Chromium OS Build Team Options",
1042 "Caution: these are for meant for the Chromium OS build team only",
1043 )
1044 group.add_argument(
1045 "--buildbot-log-version",
1046 default=False,
1047 action="store_true",
1048 help="Log SDK version for buildbot consumption",
1049 )
1050
1051 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -05001052
1053
1054def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001055 # Turn on strict sudo checks.
1056 cros_build_lib.STRICT_SUDO = True
1057 conf = key_value_store.LoadFile(
1058 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
1059 ignore_missing=True,
1060 )
1061 sdk_latest_version = conf.get("SDK_LATEST_VERSION", "<unknown>")
1062 bootstrap_frozen_version = conf.get("BOOTSTRAP_FROZEN_VERSION", "<unknown>")
Manoj Gupta55a63092019-06-13 11:47:13 -07001063
Alex Klein1699fab2022-09-08 08:46:06 -06001064 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
1065 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
1066 bootstrap_latest_version = (
1067 sdk_latest_version
1068 if bootstrap_frozen_version == "<unknown>"
1069 else bootstrap_frozen_version
1070 )
1071 parser, commands = _CreateParser(
1072 sdk_latest_version, bootstrap_latest_version
1073 )
1074 options = parser.parse_args(argv)
1075 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -08001076
Alex Klein1699fab2022-09-08 08:46:06 -06001077 # Some basic checks first, before we ask for sudo credentials.
1078 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -08001079
Alex Klein1699fab2022-09-08 08:46:06 -06001080 host = os.uname()[4]
1081 if host != "x86_64":
1082 cros_build_lib.Die(
1083 "cros_sdk is currently only supported on x86_64; you're running"
1084 " %s. Please find a x86_64 machine." % (host,)
1085 )
Brian Harring1790ac42012-09-23 08:53:33 -07001086
Alex Klein1699fab2022-09-08 08:46:06 -06001087 # Merge the outside PATH setting if we re-execed ourselves.
1088 if "CHROMEOS_SUDO_PATH" in os.environ:
1089 os.environ["PATH"] = "%s:%s" % (
1090 os.environ.pop("CHROMEOS_SUDO_PATH"),
1091 os.environ["PATH"],
1092 )
Mike Frysinger2bda4d12020-07-14 11:15:49 -04001093
Alex Klein1699fab2022-09-08 08:46:06 -06001094 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
1095 if options.proxy_sim:
1096 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
1097 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -08001098
Alex Klein1699fab2022-09-08 08:46:06 -06001099 if (
1100 sdk_latest_version == "<unknown>"
1101 or bootstrap_latest_version == "<unknown>"
1102 ):
1103 cros_build_lib.Die(
1104 "No SDK version was found. "
1105 "Are you in a Chromium source tree instead of Chromium OS?\n\n"
1106 "Please change to a directory inside your Chromium OS source tree\n"
1107 "and retry. If you need to setup a Chromium OS source tree, see\n"
1108 " https://dev.chromium.org/chromium-os/developer-guide"
1109 )
Benjamin Gordon040a1162017-06-29 13:44:47 -06001110
Alex Klein1699fab2022-09-08 08:46:06 -06001111 any_snapshot_operation = (
1112 options.snapshot_create
1113 or options.snapshot_restore
1114 or options.snapshot_delete
1115 or options.snapshot_list
1116 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001117
Alex Klein1699fab2022-09-08 08:46:06 -06001118 if (
1119 options.snapshot_delete
1120 and options.snapshot_delete == options.snapshot_restore
1121 ):
1122 parser.error(
1123 "Cannot --snapshot_delete the same snapshot you are "
1124 "restoring with --snapshot_restore."
1125 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001126
Alex Klein1699fab2022-09-08 08:46:06 -06001127 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -08001128
Alex Klein1699fab2022-09-08 08:46:06 -06001129 lock_path = os.path.dirname(options.chroot)
1130 lock_path = os.path.join(
1131 lock_path, ".%s_lock" % os.path.basename(options.chroot).lstrip(".")
1132 )
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001133
Alex Klein1699fab2022-09-08 08:46:06 -06001134 # Expand out the aliases...
1135 if options.replace:
1136 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 if options.bootstrap:
1139 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001140
Alex Klein1699fab2022-09-08 08:46:06 -06001141 # If a command is not given, default to enter.
1142 # pylint: disable=protected-access
1143 # This _group_actions access sucks, but upstream decided to not include an
1144 # alternative to optparse's option_list, and this is what they recommend.
1145 options.enter |= not any(
1146 getattr(options, x.dest) for x in commands._group_actions
1147 )
1148 # pylint: enable=protected-access
1149 options.enter |= bool(chroot_command)
Brian Harring218e13c2012-10-10 16:21:26 -07001150
Alex Klein1699fab2022-09-08 08:46:06 -06001151 if (
1152 options.delete
1153 and not options.create
1154 and (options.enter or any_snapshot_operation)
1155 ):
1156 parser.error(
1157 "Trying to enter or snapshot the chroot when --delete "
1158 "was specified makes no sense."
1159 )
Brian Harring218e13c2012-10-10 16:21:26 -07001160
Alex Klein1699fab2022-09-08 08:46:06 -06001161 if options.unmount and (
1162 options.create or options.enter or any_snapshot_operation
1163 ):
1164 parser.error("--unmount cannot be specified with other chroot actions.")
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001165
Alex Klein1699fab2022-09-08 08:46:06 -06001166 if options.working_dir is not None and not os.path.isabs(
1167 options.working_dir
1168 ):
1169 options.working_dir = path_util.ToChrootPath(options.working_dir)
Yong Hong84ba9172018-02-07 01:37:42 +08001170
Alex Klein1699fab2022-09-08 08:46:06 -06001171 # If there is an existing chroot image and we're not removing it then force
1172 # use_image on. This ensures that people don't have to remember to pass
1173 # --use-image after a reboot to avoid losing access to their existing chroot.
1174 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1175 img_path = _ImageFileForChroot(options.chroot)
1176 if (
1177 not options.use_image
1178 and not options.delete
1179 and not options.unmount
1180 and os.path.exists(img_path)
1181 ):
1182 if chroot_exists:
1183 # If the chroot is already populated, make sure it has something
1184 # mounted on it before we assume it came from an image.
1185 cmd = ["mountpoint", "-q", options.chroot]
1186 if cros_build_lib.dbg_run(cmd, check=False).returncode == 0:
1187 options.use_image = True
Benjamin Gordon832a4412020-12-08 10:39:16 -07001188
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001189 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001190 logging.notice(
1191 "Existing chroot image %s found. Forcing --use-image on.",
1192 img_path,
1193 )
1194 options.use_image = True
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001195
Alex Klein1699fab2022-09-08 08:46:06 -06001196 if any_snapshot_operation and not options.use_image:
1197 if os.path.exists(img_path):
1198 options.use_image = True
1199 else:
1200 cros_build_lib.Die(
1201 "Snapshot operations are not compatible with " "--nouse-image."
1202 )
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001203
Alex Klein1699fab2022-09-08 08:46:06 -06001204 # Discern if we need to create the chroot.
1205 if (
1206 options.use_image
1207 and not chroot_exists
1208 and not options.delete
1209 and not options.unmount
1210 and not missing_image_tools
1211 and os.path.exists(img_path)
1212 ):
1213 # Try to re-mount an existing image in case the user has rebooted.
1214 with locking.FileLock(lock_path, "chroot lock") as lock:
1215 logging.debug("Checking if existing chroot image can be mounted.")
1216 lock.write_lock()
1217 cros_sdk_lib.MountChroot(options.chroot, create=False)
1218 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1219 if chroot_exists:
1220 logging.notice("Mounted existing image %s on chroot", img_path)
1221
1222 # Finally, flip create if necessary.
1223 if options.enter or options.snapshot_create:
1224 options.create |= not chroot_exists
1225
1226 # Make sure we will download if we plan to create.
1227 options.download |= options.create
1228
1229 # Anything that needs to manipulate the main chroot mount or communicate with
1230 # LVM needs to be done here before we enter the new namespaces.
1231
1232 # If deleting, do it regardless of the use_image flag so that a
1233 # previously-created loopback chroot can also be cleaned up.
1234 if options.delete:
1235 # Set a timeout of 300 seconds when getting the lock.
1236 with locking.FileLock(
1237 lock_path, "chroot lock", blocking_timeout=300
1238 ) as lock:
1239 try:
1240 lock.write_lock()
1241 except timeout_util.TimeoutError as e:
1242 logging.error(
1243 "Acquiring write_lock on %s failed: %s", lock_path, e
1244 )
1245 if not options.force:
1246 cros_build_lib.Die(
1247 "Exiting; use --force to continue w/o lock."
1248 )
1249 else:
1250 logging.warning(
1251 "cros_sdk was invoked with force option, continuing."
1252 )
1253 logging.notice("Deleting chroot.")
1254 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
1255
1256 # If cleanup was requested, we have to do it while we're still in the original
1257 # namespace. Since cleaning up the mount will interfere with any other
1258 # commands, we exit here. The check above should have made sure that no other
1259 # action was requested, anyway.
1260 if options.unmount:
1261 # Set a timeout of 300 seconds when getting the lock.
1262 with locking.FileLock(
1263 lock_path, "chroot lock", blocking_timeout=300
1264 ) as lock:
1265 try:
1266 lock.write_lock()
1267 except timeout_util.TimeoutError as e:
1268 logging.error(
1269 "Acquiring write_lock on %s failed: %s", lock_path, e
1270 )
1271 logging.warning(
1272 "Continuing with CleanupChroot(%s), which will umount the tree.",
1273 options.chroot,
1274 )
1275 # We can call CleanupChroot (which calls cros_sdk_lib.CleanupChrootMount)
1276 # even if we don't get the lock because it will attempt to unmount the
1277 # tree and will print diagnostic information from 'fuser', 'lsof', and
1278 # 'ps'.
1279 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=False)
1280 sys.exit(0)
1281
1282 # Make sure the main chroot mount is visible. Contents will be filled in
1283 # below if needed.
1284 if options.create and options.use_image:
1285 if missing_image_tools:
1286 raise SystemExit(
1287 """The tool(s) %s were not found.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001288Please make sure the lvm2 and thin-provisioning-tools packages
1289are installed on your host.
1290Example(ubuntu):
1291 sudo apt-get install lvm2 thin-provisioning-tools
1292
1293If you want to run without lvm2, pass --nouse-image (chroot
Alex Klein1699fab2022-09-08 08:46:06 -06001294snapshots will be unavailable)."""
1295 % ", ".join(missing_image_tools)
1296 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001297
Alex Klein1699fab2022-09-08 08:46:06 -06001298 logging.debug("Making sure chroot image is mounted.")
1299 with locking.FileLock(lock_path, "chroot lock") as lock:
1300 lock.write_lock()
1301 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
1302 cros_build_lib.Die(
1303 "Unable to mount %s on chroot",
1304 _ImageFileForChroot(options.chroot),
1305 )
1306 logging.notice(
1307 "Mounted %s on chroot", _ImageFileForChroot(options.chroot)
1308 )
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001309
Alex Klein1699fab2022-09-08 08:46:06 -06001310 # Snapshot operations will always need the VG/LV, but other actions won't.
1311 if any_snapshot_operation:
1312 with locking.FileLock(lock_path, "chroot lock") as lock:
1313 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
1314 options.chroot
1315 )
1316 if not chroot_vg or not chroot_lv:
1317 cros_build_lib.Die(
1318 "Unable to find VG/LV for chroot %s", options.chroot
1319 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001320
Alex Klein1699fab2022-09-08 08:46:06 -06001321 # Delete snapshot before creating a new one. This allows the user to
1322 # throw out old state, create a new snapshot, and enter the chroot in a
1323 # single call to cros_sdk. Since restore involves deleting, also do it
1324 # before creating.
1325 if options.snapshot_restore:
1326 lock.write_lock()
1327 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1328 if options.snapshot_restore not in valid_snapshots:
1329 cros_build_lib.Die(
1330 "%s is not a valid snapshot to restore to. Valid snapshots: %s",
1331 options.snapshot_restore,
1332 ", ".join(valid_snapshots),
1333 )
1334 osutils.UmountTree(options.chroot)
1335 if not RestoreChrootSnapshot(
1336 options.snapshot_restore, chroot_vg, chroot_lv
1337 ):
1338 cros_build_lib.Die("Unable to restore chroot to snapshot.")
1339 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
1340 cros_build_lib.Die(
1341 "Unable to mount restored snapshot onto chroot."
1342 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001343
Alex Klein1699fab2022-09-08 08:46:06 -06001344 # Use a read lock for snapshot delete and create even though they modify
1345 # the filesystem, because they don't modify the mounted chroot itself.
1346 # The underlying LVM commands take their own locks, so conflicting
1347 # concurrent operations here may crash cros_sdk, but won't corrupt the
1348 # chroot image. This tradeoff seems worth it to allow snapshot
1349 # operations on chroots that have a process inside.
1350 if options.snapshot_delete:
1351 lock.read_lock()
1352 DeleteChrootSnapshot(
1353 options.snapshot_delete, chroot_vg, chroot_lv
1354 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001355
Alex Klein1699fab2022-09-08 08:46:06 -06001356 if options.snapshot_create:
1357 lock.read_lock()
1358 if not CreateChrootSnapshot(
1359 options.snapshot_create, chroot_vg, chroot_lv
1360 ):
1361 cros_build_lib.Die("Unable to create snapshot.")
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001362
Alex Klein1699fab2022-09-08 08:46:06 -06001363 img_path = _ImageFileForChroot(options.chroot)
1364 if (
1365 options.use_image
1366 and os.path.exists(options.chroot)
1367 and os.path.exists(img_path)
1368 ):
1369 img_stat = os.stat(img_path)
1370 img_used_bytes = img_stat.st_blocks * 512
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001371
Alex Klein1699fab2022-09-08 08:46:06 -06001372 mount_stat = os.statvfs(options.chroot)
1373 mount_used_bytes = mount_stat.f_frsize * (
1374 mount_stat.f_blocks - mount_stat.f_bfree
1375 )
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001376
Alex Klein1699fab2022-09-08 08:46:06 -06001377 extra_gbs = (img_used_bytes - mount_used_bytes) // 2**30
1378 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1379 logging.notice(
1380 "%s is using %s GiB more than needed. Running "
1381 "fstrim in background.",
1382 img_path,
1383 extra_gbs,
1384 )
1385 pid = os.fork()
1386 if pid == 0:
1387 try:
1388 # Directly call Popen to run fstrim concurrently.
1389 cmd = ["fstrim", options.chroot]
1390 subprocess.Popen(cmd, close_fds=True, shell=False)
1391 except subprocess.SubprocessError as e:
1392 logging.warning(
1393 "Running fstrim failed. Consider running fstrim on "
1394 "your chroot manually.\n%s",
1395 e,
1396 )
1397 os._exit(0) # pylint: disable=protected-access
1398 os.waitpid(pid, 0)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001399
Alex Klein1699fab2022-09-08 08:46:06 -06001400 # Enter a new set of namespaces. Everything after here cannot directly affect
1401 # the hosts's mounts or alter LVM volumes.
1402 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001403
Alex Klein1699fab2022-09-08 08:46:06 -06001404 if options.snapshot_list:
1405 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1406 print(snap)
1407 sys.exit(0)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001408
Alex Klein1699fab2022-09-08 08:46:06 -06001409 if not options.sdk_version:
1410 sdk_version = (
1411 bootstrap_latest_version
1412 if options.bootstrap
1413 else sdk_latest_version
1414 )
Yong Hong4e29b622018-02-05 14:31:10 +08001415 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001416 sdk_version = options.sdk_version
1417 if options.buildbot_log_version:
1418 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001419
Alex Klein1699fab2022-09-08 08:46:06 -06001420 # Based on selections, determine the tarball to fetch.
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001421 if options.download:
Alex Klein1699fab2022-09-08 08:46:06 -06001422 if options.sdk_url:
1423 urls = [options.sdk_url]
1424 else:
1425 urls = GetArchStageTarballs(sdk_version)
Brian Harring218e13c2012-10-10 16:21:26 -07001426
Alex Klein1699fab2022-09-08 08:46:06 -06001427 with locking.FileLock(lock_path, "chroot lock") as lock:
1428 if options.proxy_sim:
1429 _ProxySimSetup(options)
Brian Harring1790ac42012-09-23 08:53:33 -07001430
Alex Klein1699fab2022-09-08 08:46:06 -06001431 sdk_cache = os.path.join(options.cache_dir, "sdks")
1432 distfiles_cache = os.path.join(options.cache_dir, "distfiles")
1433 osutils.SafeMakedirsNonRoot(options.cache_dir)
1434
1435 for target in (sdk_cache, distfiles_cache):
1436 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
1437 if not os.path.exists(src):
1438 osutils.SafeMakedirsNonRoot(target)
1439 continue
1440 lock.write_lock(
1441 "Upgrade to %r needed but chroot is locked; please exit "
1442 "all instances so this upgrade can finish." % src
1443 )
1444 if not os.path.exists(src):
1445 # Note that while waiting for the write lock, src may've vanished;
1446 # it's a rare race during the upgrade process that's a byproduct
1447 # of us avoiding taking a write lock to do the src check. If we
1448 # took a write lock for that check, it would effectively limit
1449 # all cros_sdk for a chroot to a single instance.
1450 osutils.SafeMakedirsNonRoot(target)
1451 elif not os.path.exists(target):
1452 # Upgrade occurred, but a reversion, or something whacky
1453 # occurred writing to the old location. Wipe and continue.
1454 os.rename(src, target)
1455 else:
1456 # Upgrade occurred once already, but either a reversion or
1457 # some before/after separate cros_sdk usage is at play.
1458 # Wipe and continue.
1459 osutils.RmDir(src)
1460
1461 if options.download:
1462 lock.write_lock()
1463 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
1464
1465 mounted = False
1466 if options.create:
1467 lock.write_lock()
1468 # Recheck if the chroot is set up here before creating to make sure we
1469 # account for whatever the various delete/unmount/remount steps above
1470 # have done.
1471 if cros_sdk_lib.IsChrootReady(options.chroot):
1472 logging.debug("Chroot already exists. Skipping creation.")
1473 else:
1474 cros_sdk_lib.CreateChroot(
1475 Path(options.chroot),
1476 Path(sdk_tarball),
1477 Path(options.cache_dir),
1478 usepkg=not options.bootstrap and not options.nousepkg,
1479 chroot_upgrade=options.chroot_upgrade,
1480 )
1481 mounted = True
1482
1483 if options.enter:
1484 lock.read_lock()
1485 if not mounted:
1486 cros_sdk_lib.MountChrootPaths(options.chroot)
1487 ret = EnterChroot(
1488 options.chroot,
1489 options.cache_dir,
1490 options.chrome_root,
1491 options.chrome_root_mount,
1492 options.goma_dir,
1493 options.goma_client_json,
1494 options.reclient_dir,
1495 options.reproxy_cfg_file,
1496 options.working_dir,
1497 chroot_command,
1498 )
1499 sys.exit(ret.returncode)