blob: 83961374e4138958036458db0e6abeed15096e32 [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
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 Frysingerec32bea2023-01-27 01:20:48 -050027from 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
Mike Frysingerec32bea2023-01-27 01:20:48 -050031from chromite.lib import chroot_lib
Brian Harringb6cf9142012-09-01 20:43:17 -070032from chromite.lib import commandline
Chris McDonaldb55b7032021-06-17 16:41:32 -060033from chromite.lib import constants
Brian Harringb938c782012-02-29 15:14:38 -080034from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060035from chromite.lib import cros_sdk_lib
Mike Frysinger9a5124e2023-01-26 17:16:44 -050036from chromite.lib import goma_lib
Brian Harringb938c782012-02-29 15:14:38 -080037from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080038from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070039from chromite.lib import osutils
Yong Hong84ba9172018-02-07 01:37:42 +080040from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040041from chromite.lib import process_util
Mike Frysinger253c8392023-01-27 01:12:21 -050042from chromite.lib import remoteexec_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080043from chromite.lib import retry_util
Michael Mortensenbf296fb2020-06-18 18:21:54 -060044from chromite.lib import timeout_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050045from chromite.lib import toolchain
Mike Frysingere652ba12019-09-08 00:57:43 -040046from chromite.utils import key_value_store
47
Brian Harringb938c782012-02-29 15:14:38 -080048
Mike Frysingerf744d032022-05-07 20:38:39 -040049# Which compression algos the SDK tarball uses. We've used xz since 2012.
Alex Klein1699fab2022-09-08 08:46:06 -060050COMPRESSION_PREFERENCE = ("xz",)
Zdenek Behanfd0efe42012-04-13 04:36:40 +020051
Brian Harringb938c782012-02-29 15:14:38 -080052# TODO(zbehan): Remove the dependency on these, reimplement them in python
Manoj Guptab12f7302019-06-03 16:40:14 -070053ENTER_CHROOT = [
Alex Klein1699fab2022-09-08 08:46:06 -060054 os.path.join(constants.SOURCE_ROOT, "src/scripts/sdk_lib/enter_chroot.sh")
Manoj Guptab12f7302019-06-03 16:40:14 -070055]
Brian Harringb938c782012-02-29 15:14:38 -080056
Josh Triplett472a4182013-03-08 11:48:57 -080057# Proxy simulator configuration.
Alex Klein1699fab2022-09-08 08:46:06 -060058PROXY_HOST_IP = "192.168.240.1"
Josh Triplett472a4182013-03-08 11:48:57 -080059PROXY_PORT = 8080
Alex Klein1699fab2022-09-08 08:46:06 -060060PROXY_GUEST_IP = "192.168.240.2"
Josh Triplett472a4182013-03-08 11:48:57 -080061PROXY_NETMASK = 30
Alex Klein1699fab2022-09-08 08:46:06 -060062PROXY_VETH_PREFIX = "veth"
Josh Triplett472a4182013-03-08 11:48:57 -080063PROXY_CONNECT_PORTS = (80, 443, 9418)
Alex Klein1699fab2022-09-08 08:46:06 -060064PROXY_APACHE_FALLBACK_USERS = ("www-data", "apache", "nobody")
65PROXY_APACHE_MPMS = ("event", "worker", "prefork")
66PROXY_APACHE_FALLBACK_PATH = ":".join(
67 "/usr/lib/apache2/mpm-%s" % mpm for mpm in PROXY_APACHE_MPMS
68)
69PROXY_APACHE_MODULE_GLOBS = ("/usr/lib*/apache2/modules", "/usr/lib*/apache2")
Josh Triplett472a4182013-03-08 11:48:57 -080070
Josh Triplett9a495f62013-03-15 18:06:55 -070071# We need these tools to run. Very common tools (tar,..) are omitted.
Alex Klein1699fab2022-09-08 08:46:06 -060072NEEDED_TOOLS = ("curl", "xz")
Brian Harringb938c782012-02-29 15:14:38 -080073
Josh Triplett472a4182013-03-08 11:48:57 -080074# Tools needed for --proxy-sim only.
Alex Klein1699fab2022-09-08 08:46:06 -060075PROXY_NEEDED_TOOLS = ("ip",)
Brian Harringb938c782012-02-29 15:14:38 -080076
Mike Frysingerc60141b2023-01-27 00:57:15 -050077# Tools needed when use_image is true.
Alex Klein1699fab2022-09-08 08:46:06 -060078IMAGE_NEEDED_TOOLS = (
79 "losetup",
80 "lvchange",
81 "lvcreate",
82 "lvs",
83 "mke2fs",
84 "pvscan",
85 "thin_check",
86 "vgchange",
87 "vgcreate",
88 "vgs",
89)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060090
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070091# As space is used inside the chroot, the empty space in chroot.img is
92# allocated. Deleting files inside the chroot doesn't automatically return the
93# used space to the OS. Over time, this tends to make the sparse chroot.img
94# less sparse even if the chroot contents don't currently need much space. We
95# can recover most of this unused space with fstrim, but that takes too much
96# time to run it every time. Instead, check the used space against the image
97# size after mounting the chroot and only call fstrim if it looks like we could
98# recover at least this many GiB.
99MAX_UNUSED_IMAGE_GBS = 20
100
Mike Frysingercc838832014-05-24 13:10:30 -0400101
Brian Harring1790ac42012-09-23 08:53:33 -0700102def GetArchStageTarballs(version):
Alex Klein1699fab2022-09-08 08:46:06 -0600103 """Returns the URL for a given arch/version"""
104 extension = {"xz": "tar.xz"}
105 return [
106 toolchain.GetSdkURL(
107 suburl="cros-sdk-%s.%s" % (version, extension[compressor])
108 )
109 for compressor in COMPRESSION_PREFERENCE
110 ]
Brian Harring1790ac42012-09-23 08:53:33 -0700111
112
Mike Frysingerf744d032022-05-07 20:38:39 -0400113def FetchRemoteTarballs(storage_dir, urls):
Alex Klein1699fab2022-09-08 08:46:06 -0600114 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200115
Alex Klein1699fab2022-09-08 08:46:06 -0600116 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700117 storage_dir: Path where to save the tarball.
118 urls: List of URLs to try to download. Download will stop on first
119 success.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700122 Full path to the downloaded file.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700123
Alex Klein1699fab2022-09-08 08:46:06 -0600124 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700125 ValueError: None of the URLs worked.
Alex Klein1699fab2022-09-08 08:46:06 -0600126 """
127 # Note we track content length ourselves since certain versions of curl
128 # fail if asked to resume a complete file.
129 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
130 status_re = re.compile(rb"^HTTP/[0-9]+(\.[0-9]+)? 200")
131 # pylint: disable=undefined-loop-variable
132 for url in urls:
133 logging.notice("Downloading tarball %s ...", urls[0].rsplit("/", 1)[-1])
134 parsed = urllib.parse.urlparse(url)
135 tarball_name = os.path.basename(parsed.path)
136 if parsed.scheme in ("", "file"):
137 if os.path.exists(parsed.path):
138 return parsed.path
139 continue
140 content_length = 0
141 logging.debug("Attempting download from %s", url)
142 result = retry_util.RunCurl(
143 ["-I", url],
144 print_cmd=False,
145 debug_level=logging.NOTICE,
146 capture_output=True,
147 )
148 successful = False
149 for header in result.stdout.splitlines():
150 # We must walk the output to find the 200 code for use cases where
151 # a proxy is involved and may have pushed down the actual header.
152 if status_re.match(header):
153 successful = True
154 elif header.lower().startswith(b"content-length:"):
155 content_length = int(header.split(b":", 1)[-1].strip())
156 if successful:
157 break
Brian Harring1790ac42012-09-23 08:53:33 -0700158 if successful:
Alex Klein1699fab2022-09-08 08:46:06 -0600159 break
160 else:
161 raise ValueError("No valid URLs found!")
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 tarball_dest = os.path.join(storage_dir, tarball_name)
164 current_size = 0
165 if os.path.exists(tarball_dest):
166 current_size = os.path.getsize(tarball_dest)
167 if current_size > content_length:
168 osutils.SafeUnlink(tarball_dest)
169 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 if current_size < content_length:
172 retry_util.RunCurl(
173 [
174 "--fail",
175 "-L",
176 "-y",
177 "30",
178 "-C",
179 "-",
180 "--output",
181 tarball_dest,
182 url,
183 ],
184 print_cmd=False,
185 debug_level=logging.NOTICE,
186 )
Brian Harringb938c782012-02-29 15:14:38 -0800187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 # Cleanup old tarballs now since we've successfull fetched; only cleanup
189 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
190 # because we might have partial overlap between known prefixes.
191 for p in Path(storage_dir).glob("cros-sdk-*"):
192 if p.name == tarball_name:
193 continue
194 logging.info("Cleaning up old tarball: %s", p)
195 osutils.SafeUnlink(p)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 return tarball_dest
Brian Harringb938c782012-02-29 15:14:38 -0800198
199
Alex Klein1699fab2022-09-08 08:46:06 -0600200def EnterChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -0500201 chroot: chroot_lib.Chroot,
Alex Klein1699fab2022-09-08 08:46:06 -0600202 chrome_root_mount,
Alex Klein1699fab2022-09-08 08:46:06 -0600203 working_dir,
204 additional_args,
205):
206 """Enters an existing SDK chroot"""
Mike Frysingerec32bea2023-01-27 01:20:48 -0500207 st = os.statvfs(os.path.join(chroot.path, "usr", "bin", "sudo"))
Alex Klein1699fab2022-09-08 08:46:06 -0600208 if st.f_flag & os.ST_NOSUID:
209 cros_build_lib.Die("chroot cannot be in a nosuid mount")
Mike Frysingere5456972013-06-13 00:07:23 -0400210
Mike Frysinger21956622023-01-31 02:44:49 -0500211 cmd = ENTER_CHROOT + chroot.get_enter_args(for_shell=True)
Alex Klein1699fab2022-09-08 08:46:06 -0600212 if chrome_root_mount:
213 cmd.extend(["--chrome_root_mount", chrome_root_mount])
Alex Klein1699fab2022-09-08 08:46:06 -0600214 if working_dir is not None:
215 cmd.extend(["--working_dir", working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700216
Alex Klein1699fab2022-09-08 08:46:06 -0600217 if additional_args:
218 cmd.append("--")
219 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700220
Alex Klein1699fab2022-09-08 08:46:06 -0600221 if "CHROMEOS_SUDO_RLIMITS" in os.environ:
222 _SetRlimits(os.environ.pop("CHROMEOS_SUDO_RLIMITS"))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400223
Alex Klein1699fab2022-09-08 08:46:06 -0600224 # Some systems set the soft limit too low. Bump it up to the hard limit.
225 # We don't override the hard limit because it's something the admins put
226 # in place and we want to respect such configs. http://b/234353695
227 soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
228 if soft != resource.RLIM_INFINITY and soft < 4096:
229 if soft < hard or hard == resource.RLIM_INFINITY:
230 resource.setrlimit(resource.RLIMIT_NPROC, (hard, hard))
Mike Frysingerebb23fc2022-06-06 21:17:39 -0400231
Alex Klein1699fab2022-09-08 08:46:06 -0600232 # ThinLTO opens lots of files at the same time.
233 # Set rlimit and vm.max_map_count to accommodate this.
234 file_limit = 262144
235 soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
236 resource.setrlimit(
237 resource.RLIMIT_NOFILE, (max(soft, file_limit), max(hard, file_limit))
238 )
239 max_map_count = int(osutils.ReadFile("/proc/sys/vm/max_map_count"))
240 if max_map_count < file_limit:
241 logging.notice(
242 "Raising vm.max_map_count from %s to %s", max_map_count, file_limit
243 )
244 osutils.WriteFile("/proc/sys/vm/max_map_count", str(file_limit))
245 return cros_build_lib.dbg_run(cmd, check=False)
Brian Harringb938c782012-02-29 15:14:38 -0800246
247
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600248def _ImageFileForChroot(chroot):
Alex Klein1699fab2022-09-08 08:46:06 -0600249 """Find the image file that should be associated with |chroot|.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 This function does not check if the image exists; it simply returns the
252 filename that would be used.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700255 chroot: Path to the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700258 Path to an image file that would be associated with chroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600259 """
260 return chroot.rstrip("/") + ".img"
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600261
262
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600263def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600264 """Create a snapshot for the specified chroot VG/LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700267 snapshot_name: The name of the new snapshot.
268 chroot_vg: The name of the VG containing the origin LV.
269 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700272 True if the snapshot was created, or False if a snapshot with the same
273 name already exists.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600274
Alex Klein1699fab2022-09-08 08:46:06 -0600275 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700276 SystemExit: The lvcreate command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600277 """
278 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
279 logging.error(
280 "Cannot create snapshot %s: A volume with that name already "
281 "exists.",
282 snapshot_name,
283 )
284 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600285
Alex Klein1699fab2022-09-08 08:46:06 -0600286 cmd = [
287 "lvcreate",
288 "-s",
289 "--name",
290 snapshot_name,
291 "%s/%s" % (chroot_vg, chroot_lv),
292 ]
293 try:
294 logging.notice(
295 "Creating snapshot %s from %s in VG %s.",
296 snapshot_name,
297 chroot_lv,
298 chroot_vg,
299 )
300 cros_build_lib.dbg_run(cmd, capture_output=True)
301 return True
302 except cros_build_lib.RunCommandError as e:
303 cros_build_lib.Die("Creating snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600304
305
306def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600307 """Delete the named snapshot from the specified chroot VG.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 If the requested snapshot is not found, nothing happens. The main chroot LV
310 and internal thinpool LV cannot be deleted with this function.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600311
Alex Klein1699fab2022-09-08 08:46:06 -0600312 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700313 snapshot_name: The name of the snapshot to delete.
314 chroot_vg: The name of the VG containing the origin LV.
315 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700318 SystemExit: The lvremove command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600319 """
320 if snapshot_name in (
321 cros_sdk_lib.CHROOT_LV_NAME,
322 cros_sdk_lib.CHROOT_THINPOOL_NAME,
323 ):
324 logging.error(
325 "Cannot remove LV %s as a snapshot. Use cros_sdk --delete "
326 "if you want to remove the whole chroot.",
327 snapshot_name,
328 )
329 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600330
Alex Klein1699fab2022-09-08 08:46:06 -0600331 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
332 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, snapshot_name)]
335 try:
336 logging.notice(
337 "Deleting snapshot %s in VG %s.", snapshot_name, chroot_vg
338 )
339 cros_build_lib.dbg_run(cmd, capture_output=True)
340 except cros_build_lib.RunCommandError as e:
341 cros_build_lib.Die("Deleting snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600342
343
344def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600345 """Restore the chroot to an existing snapshot.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600346
Alex Klein1699fab2022-09-08 08:46:06 -0600347 This is done by renaming the original |chroot_lv| LV to a temporary name,
348 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
349 now unused LV. If an error occurs, attempts to rename the original snapshot
350 back to |chroot_lv| to leave the chroot unchanged.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600351
Alex Klein1699fab2022-09-08 08:46:06 -0600352 The chroot must be unmounted before calling this function, and will be left
353 unmounted after this function returns.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600354
Alex Klein1699fab2022-09-08 08:46:06 -0600355 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700356 snapshot_name: The name of the snapshot to restore. This snapshot will
357 no longer be accessible at its original name after this function
358 finishes.
359 chroot_vg: The VG containing the chroot LV and snapshot LV.
360 chroot_lv: The name of the original chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600361
Alex Klein1699fab2022-09-08 08:46:06 -0600362 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700363 True if the chroot was restored to the requested snapshot, or False if
364 the snapshot wasn't found or isn't valid.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600365
Alex Klein1699fab2022-09-08 08:46:06 -0600366 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700367 SystemExit: Any of the LVM commands failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600368 """
369 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
370 if (
371 snapshot_name
372 in (cros_sdk_lib.CHROOT_LV_NAME, cros_sdk_lib.CHROOT_THINPOOL_NAME)
373 or snapshot_name not in valid_snapshots
374 ):
375 logging.error(
376 "Chroot cannot be restored to %s. Valid snapshots: %s",
377 snapshot_name,
378 ", ".join(valid_snapshots),
379 )
380 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600381
Alex Klein1699fab2022-09-08 08:46:06 -0600382 backup_chroot_name = "chroot-bak-%d" % random.randint(0, 1000)
383 cmd = ["lvrename", chroot_vg, chroot_lv, backup_chroot_name]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600384 try:
Alex Klein1699fab2022-09-08 08:46:06 -0600385 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500386 except cros_build_lib.RunCommandError as e:
Alex Klein1699fab2022-09-08 08:46:06 -0600387 cros_build_lib.Die("Restoring snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600388
Alex Klein1699fab2022-09-08 08:46:06 -0600389 cmd = ["lvrename", chroot_vg, snapshot_name, chroot_lv]
390 try:
391 cros_build_lib.dbg_run(cmd, capture_output=True)
392 except cros_build_lib.RunCommandError as e:
393 cmd = ["lvrename", chroot_vg, backup_chroot_name, chroot_lv]
394 try:
395 cros_build_lib.dbg_run(cmd, capture_output=True)
396 except cros_build_lib.RunCommandError as e:
397 cros_build_lib.Die(
Alex Kleinef517832023-01-13 12:06:51 -0700398 "Failed to rename %s to chroot and failed to restore %s back "
399 "to chroot!\n%s",
Alex Klein1699fab2022-09-08 08:46:06 -0600400 snapshot_name,
401 backup_chroot_name,
402 e,
403 )
404 cros_build_lib.Die(
405 "Failed to rename %s to chroot! Original chroot LV has "
406 "been restored.\n%s",
407 snapshot_name,
408 e,
409 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600410
Alex Klein1699fab2022-09-08 08:46:06 -0600411 # Some versions of LVM set snapshots to be skipped at auto-activate time.
412 # Other versions don't have this flag at all. We run lvchange to try
Alex Kleinef517832023-01-13 12:06:51 -0700413 # disabling auto-skip and activating the volume, but ignore errors. Versions
Alex Klein1699fab2022-09-08 08:46:06 -0600414 # that don't have the flag should be auto-activated.
415 chroot_lv_path = "%s/%s" % (chroot_vg, chroot_lv)
416 cmd = ["lvchange", "-kn", chroot_lv_path]
417 cros_build_lib.run(cmd, print_cmd=False, capture_output=True, check=False)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600418
Alex Klein1699fab2022-09-08 08:46:06 -0600419 # Activate the LV in case the lvchange above was needed. Activating an LV
Alex Kleinef517832023-01-13 12:06:51 -0700420 # that is already active shouldn't do anything, so this is safe to run even
421 # if the -kn wasn't needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600422 cmd = ["lvchange", "-ay", chroot_lv_path]
Mike Frysinger3e8de442020-02-14 16:46:28 -0500423 cros_build_lib.dbg_run(cmd, capture_output=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, backup_chroot_name)]
426 try:
427 cros_build_lib.dbg_run(cmd, capture_output=True)
428 except cros_build_lib.RunCommandError as e:
429 cros_build_lib.Die(
430 "Failed to remove backup LV %s/%s!\n%s",
431 chroot_vg,
432 backup_chroot_name,
433 e,
434 )
435
436 return True
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600437
438
439def ListChrootSnapshots(chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600440 """Return all snapshots in |chroot_vg| regardless of origin volume.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600441
Alex Klein1699fab2022-09-08 08:46:06 -0600442 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700443 chroot_vg: The name of the VG containing the chroot.
444 chroot_lv: The name of the chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600445
Alex Klein1699fab2022-09-08 08:46:06 -0600446 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700447 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600448
Alex Klein1699fab2022-09-08 08:46:06 -0600449 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700450 SystemExit: The lvs command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600451 """
452 if not chroot_vg or not chroot_lv:
453 return []
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 cmd = [
456 "lvs",
457 "-o",
458 "lv_name,pool_lv,lv_attr",
459 "-O",
460 "lv_name",
461 "--noheadings",
462 "--separator",
463 "\t",
464 chroot_vg,
465 ]
466 try:
467 result = cros_build_lib.run(
468 cmd, print_cmd=False, stdout=True, encoding="utf-8"
469 )
470 except cros_build_lib.RunCommandError:
471 raise SystemExit("Running %r failed!" % cmd)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 # Once the thin origin volume has been deleted, there's no way to tell a
Alex Kleinef517832023-01-13 12:06:51 -0700474 # snapshot apart from any other volume. Since this VG is created and managed
475 # by cros_sdk, we'll assume that all volumes that share the same thin pool
476 # are valid snapshots.
Alex Klein1699fab2022-09-08 08:46:06 -0600477 snapshots = []
478 snapshot_attrs = re.compile(r"^V.....t.{2,}") # Matches a thin volume.
479 for line in result.stdout.splitlines():
480 lv_name, pool_lv, lv_attr = line.lstrip().split("\t")
481 if (
482 lv_name == chroot_lv
483 or lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME
484 or pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME
485 or not snapshot_attrs.match(lv_attr)
486 ):
487 continue
488 snapshots.append(lv_name)
489 return snapshots
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600490
491
Mike Frysinger12d055b2022-06-23 12:26:47 -0400492# The rlimits we will lookup & pass down, in order.
493RLIMITS_TO_PASS = (
494 resource.RLIMIT_AS,
495 resource.RLIMIT_CORE,
496 resource.RLIMIT_CPU,
497 resource.RLIMIT_FSIZE,
498 resource.RLIMIT_MEMLOCK,
499 resource.RLIMIT_NICE,
500 resource.RLIMIT_NOFILE,
501 resource.RLIMIT_NPROC,
502 resource.RLIMIT_RSS,
503 resource.RLIMIT_STACK,
504)
505
506
507def _GetRlimits() -> str:
Alex Klein1699fab2022-09-08 08:46:06 -0600508 """Serialize current rlimits."""
509 return str(tuple(resource.getrlimit(x) for x in RLIMITS_TO_PASS))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400510
511
512def _SetRlimits(limits: str) -> None:
Alex Klein1699fab2022-09-08 08:46:06 -0600513 """Deserialize rlimits."""
514 for rlim, limit in zip(RLIMITS_TO_PASS, ast.literal_eval(limits)):
515 cur_limit = resource.getrlimit(rlim)
516 if cur_limit != limit:
517 # Turn the number into a symbolic name for logging.
518 name = "RLIMIT_???"
519 for name, num in resource.__dict__.items():
520 if name.startswith("RLIMIT_") and num == rlim:
521 break
522 logging.debug(
523 "Restoring user rlimit %s from %r to %r", name, cur_limit, limit
524 )
Mike Frysinger12d055b2022-06-23 12:26:47 -0400525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 resource.setrlimit(rlim, limit)
Mike Frysinger12d055b2022-06-23 12:26:47 -0400527
528
David James56e6c2c2012-10-24 23:54:41 -0700529def _SudoCommand():
Alex Klein1699fab2022-09-08 08:46:06 -0600530 """Get the 'sudo' command, along with all needed environment variables."""
David James56e6c2c2012-10-24 23:54:41 -0700531
Alex Klein1699fab2022-09-08 08:46:06 -0600532 # Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
533 # scripts in the chroot know what variables to pass through.
534 cmd = ["sudo"]
535 for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
536 value = os.environ.get(key)
537 if value is not None:
538 cmd += ["%s=%s" % (key, value)]
David James56e6c2c2012-10-24 23:54:41 -0700539
Alex Kleinef517832023-01-13 12:06:51 -0700540 # We keep PATH not for the chroot but for the re-exec & for programs we
541 # might run before we chroot into the SDK. The process that enters the SDK
542 # itself will take care of initializing PATH to the right value then. But
543 # we can't override the system's default PATH for root as that will hide
544 # /sbin.
Alex Klein1699fab2022-09-08 08:46:06 -0600545 cmd += ["CHROMEOS_SUDO_PATH=%s" % os.environ.get("PATH", "")]
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400546
Alex Klein1699fab2022-09-08 08:46:06 -0600547 # Pass along current rlimit settings so we can restore them.
548 cmd += [f"CHROMEOS_SUDO_RLIMITS={_GetRlimits()}"]
Mike Frysinger12d055b2022-06-23 12:26:47 -0400549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 # Pass in the path to the depot_tools so that users can access them from
551 # within the chroot.
552 cmd += ["DEPOT_TOOLS=%s" % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500553
Alex Klein1699fab2022-09-08 08:46:06 -0600554 return cmd
David James56e6c2c2012-10-24 23:54:41 -0700555
556
Josh Triplett472a4182013-03-08 11:48:57 -0800557def _ReportMissing(missing):
Alex Klein1699fab2022-09-08 08:46:06 -0600558 """Report missing utilities, then exit.
Josh Triplett472a4182013-03-08 11:48:57 -0800559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700561 missing: List of missing utilities, as returned by
562 osutils.FindMissingBinaries. If non-empty, will not return.
Alex Klein1699fab2022-09-08 08:46:06 -0600563 """
Josh Triplett472a4182013-03-08 11:48:57 -0800564
Alex Klein1699fab2022-09-08 08:46:06 -0600565 if missing:
566 raise SystemExit(
567 "The tool(s) %s were not found.\n"
568 "Please install the appropriate package in your host.\n"
569 "Example(ubuntu):\n"
570 " sudo apt-get install <packagename>" % ", ".join(missing)
571 )
Josh Triplett472a4182013-03-08 11:48:57 -0800572
573
574def _ProxySimSetup(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600575 """Set up proxy simulator, and return only in the child environment.
Josh Triplett472a4182013-03-08 11:48:57 -0800576
Alex Klein1699fab2022-09-08 08:46:06 -0600577 TODO: Ideally, this should support multiple concurrent invocations of
578 cros_sdk --proxy-sim; currently, such invocations will conflict with each
579 other due to the veth device names and IP addresses. Either this code would
580 need to generate fresh, unused names for all of these before forking, or it
581 would need to support multiple concurrent cros_sdk invocations sharing one
582 proxy and allowing it to exit when unused (without counting on any local
583 service-management infrastructure on the host).
584 """
Josh Triplett472a4182013-03-08 11:48:57 -0800585
Alex Klein1699fab2022-09-08 08:46:06 -0600586 may_need_mpm = False
587 apache_bin = osutils.Which("apache2")
Josh Triplett472a4182013-03-08 11:48:57 -0800588 if apache_bin is None:
Alex Klein1699fab2022-09-08 08:46:06 -0600589 apache_bin = osutils.Which("apache2", PROXY_APACHE_FALLBACK_PATH)
590 if apache_bin is None:
591 _ReportMissing(("apache2",))
592 else:
593 may_need_mpm = True
Josh Triplett472a4182013-03-08 11:48:57 -0800594
Alex Klein1699fab2022-09-08 08:46:06 -0600595 # Module names and .so names included for ease of grepping.
596 apache_modules = [
597 ("proxy_module", "mod_proxy.so"),
598 ("proxy_connect_module", "mod_proxy_connect.so"),
599 ("proxy_http_module", "mod_proxy_http.so"),
600 ("proxy_ftp_module", "mod_proxy_ftp.so"),
601 ]
Josh Triplett472a4182013-03-08 11:48:57 -0800602
Alex Kleinef517832023-01-13 12:06:51 -0700603 # Find the apache module directory and make sure it has the modules we need.
Alex Klein1699fab2022-09-08 08:46:06 -0600604 module_dirs = {}
605 for g in PROXY_APACHE_MODULE_GLOBS:
606 for _, so in apache_modules:
607 for f in glob.glob(os.path.join(g, so)):
608 module_dirs.setdefault(os.path.dirname(f), []).append(so)
609 for apache_module_path, modules_found in module_dirs.items():
610 if len(modules_found) == len(apache_modules):
611 break
612 else:
Alex Kleinef517832023-01-13 12:06:51 -0700613 # Appease cros lint, which doesn't understand that this else block will
614 # not fall through to the subsequent code which relies on
615 # apache_module_path.
Alex Klein1699fab2022-09-08 08:46:06 -0600616 apache_module_path = None
617 raise SystemExit(
Alex Kleinef517832023-01-13 12:06:51 -0700618 "Could not find apache module path containing all required "
619 "modules: %s" % ", ".join(so for mod, so in apache_modules)
Alex Klein1699fab2022-09-08 08:46:06 -0600620 )
Josh Triplett472a4182013-03-08 11:48:57 -0800621
Alex Klein1699fab2022-09-08 08:46:06 -0600622 def check_add_module(name):
623 so = "mod_%s.so" % name
624 if os.access(os.path.join(apache_module_path, so), os.F_OK):
625 mod = "%s_module" % name
626 apache_modules.append((mod, so))
627 return True
628 return False
Josh Triplett472a4182013-03-08 11:48:57 -0800629
Alex Klein1699fab2022-09-08 08:46:06 -0600630 check_add_module("authz_core")
631 if may_need_mpm:
632 for mpm in PROXY_APACHE_MPMS:
633 if check_add_module("mpm_%s" % mpm):
634 break
Josh Triplett472a4182013-03-08 11:48:57 -0800635
Alex Klein1699fab2022-09-08 08:46:06 -0600636 veth_host = "%s-host" % PROXY_VETH_PREFIX
637 veth_guest = "%s-guest" % PROXY_VETH_PREFIX
Josh Triplett472a4182013-03-08 11:48:57 -0800638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 # Set up locks to sync the net namespace setup. We need the child to create
Alex Kleinef517832023-01-13 12:06:51 -0700640 # the net ns first, and then have the parent assign the guest end of the
641 # veth interface to the child's new network namespace & bring up the proxy.
642 # Only then can the child move forward and rely on the network being up.
Alex Klein1699fab2022-09-08 08:46:06 -0600643 ns_create_lock = locking.PipeLock()
644 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800645
Alex Klein1699fab2022-09-08 08:46:06 -0600646 pid = os.fork()
647 if not pid:
648 # Create our new isolated net namespace.
649 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500650
Alex Klein1699fab2022-09-08 08:46:06 -0600651 # Signal the parent the ns is ready to be configured.
652 ns_create_lock.Post()
653 del ns_create_lock
654
655 # Wait for the parent to finish setting up the ns/proxy.
656 ns_setup_lock.Wait()
657 del ns_setup_lock
658
659 # Set up child side of the network.
660 commands = (
661 ("ip", "link", "set", "up", "lo"),
662 (
663 "ip",
664 "address",
665 "add",
666 "%s/%u" % (PROXY_GUEST_IP, PROXY_NETMASK),
667 "dev",
668 veth_guest,
669 ),
670 ("ip", "link", "set", veth_guest, "up"),
671 )
672 try:
673 for cmd in commands:
674 cros_build_lib.dbg_run(cmd)
675 except cros_build_lib.RunCommandError as e:
676 cros_build_lib.Die("Proxy setup failed!\n%s", e)
677
678 proxy_url = "http://%s:%u" % (PROXY_HOST_IP, PROXY_PORT)
679 for proto in ("http", "https", "ftp"):
680 os.environ[proto + "_proxy"] = proxy_url
681 for v in ("all_proxy", "RSYNC_PROXY", "no_proxy"):
682 os.environ.pop(v, None)
683 return
684
685 # Set up parent side of the network.
686 uid = int(os.environ.get("SUDO_UID", "0"))
687 gid = int(os.environ.get("SUDO_GID", "0"))
688 if uid == 0 or gid == 0:
689 for username in PROXY_APACHE_FALLBACK_USERS:
690 try:
691 pwnam = pwd.getpwnam(username)
692 uid, gid = pwnam.pw_uid, pwnam.pw_gid
693 break
694 except KeyError:
695 continue
696 if uid == 0 or gid == 0:
697 raise SystemExit("Could not find a non-root user to run Apache as")
698
699 chroot_parent, chroot_base = os.path.split(options.chroot)
700 pid_file = os.path.join(chroot_parent, ".%s-apache-proxy.pid" % chroot_base)
701 log_file = os.path.join(chroot_parent, ".%s-apache-proxy.log" % chroot_base)
702
703 # Wait for the child to create the net ns.
704 ns_create_lock.Wait()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500705 del ns_create_lock
706
Alex Klein1699fab2022-09-08 08:46:06 -0600707 apache_directives = [
708 "User #%u" % uid,
709 "Group #%u" % gid,
710 "PidFile %s" % pid_file,
711 "ErrorLog %s" % log_file,
712 "Listen %s:%u" % (PROXY_HOST_IP, PROXY_PORT),
713 "ServerName %s" % PROXY_HOST_IP,
714 "ProxyRequests On",
715 "AllowCONNECT %s" % " ".join(str(x) for x in PROXY_CONNECT_PORTS),
716 ] + [
717 "LoadModule %s %s" % (mod, os.path.join(apache_module_path, so))
718 for (mod, so) in apache_modules
719 ]
720 commands = (
721 (
722 "ip",
723 "link",
724 "add",
725 "name",
726 veth_host,
727 "type",
728 "veth",
729 "peer",
730 "name",
731 veth_guest,
732 ),
733 (
734 "ip",
735 "address",
736 "add",
737 "%s/%u" % (PROXY_HOST_IP, PROXY_NETMASK),
738 "dev",
739 veth_host,
740 ),
741 ("ip", "link", "set", veth_host, "up"),
742 (
743 [apache_bin, "-f", "/dev/null"]
744 + [arg for d in apache_directives for arg in ("-C", d)]
745 ),
746 ("ip", "link", "set", veth_guest, "netns", str(pid)),
747 )
748 cmd = None # Make cros lint happy.
749 try:
750 for cmd in commands:
751 cros_build_lib.dbg_run(cmd)
752 except cros_build_lib.RunCommandError as e:
753 # Clean up existing interfaces, if any.
754 cmd_cleanup = ("ip", "link", "del", veth_host)
755 try:
756 cros_build_lib.run(cmd_cleanup, print_cmd=False)
757 except cros_build_lib.RunCommandError:
758 logging.error("running %r failed", cmd_cleanup)
759 cros_build_lib.Die("Proxy network setup failed!\n%s", e)
760
761 # Signal the child that the net ns/proxy is fully configured now.
762 ns_setup_lock.Post()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500763 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800764
Alex Klein1699fab2022-09-08 08:46:06 -0600765 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800766
767
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400768def _BuildReExecCommand(argv, opts) -> List[str]:
Alex Klein1699fab2022-09-08 08:46:06 -0600769 """Generate new command for self-reexec."""
770 # Make sure to preserve the active Python executable in case the version
771 # we're running as is not the default one found via the (new) $PATH.
772 cmd = _SudoCommand() + ["--"]
773 if opts.strace:
774 cmd += ["strace"] + shlex.split(opts.strace_arguments) + ["--"]
775 return cmd + [sys.executable] + argv
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400776
777
778def _ReExecuteIfNeeded(argv, opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600779 """Re-execute cros_sdk as root.
David James56e6c2c2012-10-24 23:54:41 -0700780
Alex Klein1699fab2022-09-08 08:46:06 -0600781 Also unshare the mount namespace so as to ensure that processes outside
782 the chroot can't mess with our mounts.
783 """
784 if osutils.IsNonRootUser():
785 cmd = _BuildReExecCommand(argv, opts)
786 logging.debug(
787 "Reexecing self via sudo:\n%s", cros_build_lib.CmdToStr(cmd)
788 )
789 os.execvp(cmd[0], cmd)
David James56e6c2c2012-10-24 23:54:41 -0700790
791
Mike Frysinger34db8692013-11-11 14:54:08 -0500792def _CreateParser(sdk_latest_version, bootstrap_latest_version):
Alex Klein1699fab2022-09-08 08:46:06 -0600793 """Generate and return the parser with all the options."""
794 usage = (
795 "usage: %(prog)s [options] "
796 "[VAR1=val1 ... VAR2=val2] [--] [command [args]]"
797 )
798 parser = commandline.ArgumentParser(
799 usage=usage, description=__doc__, caching=True
800 )
Brian Harring218e13c2012-10-10 16:21:26 -0700801
Alex Klein1699fab2022-09-08 08:46:06 -0600802 # Global options.
803 default_chroot = os.path.join(
804 constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR
805 )
806 parser.add_argument(
807 "--chroot",
808 dest="chroot",
809 default=default_chroot,
810 type="path",
811 help=("SDK chroot dir name [%s]" % constants.DEFAULT_CHROOT_DIR),
812 )
813 parser.add_argument(
Dan Callaghanada9e032023-01-30 14:28:46 +1100814 "--out-dir",
815 metavar="DIR",
816 default=constants.DEFAULT_OUT_DIR,
817 type=Path,
818 help="Use DIR for build state and output files",
819 )
820 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600821 "--nouse-image",
822 dest="use_image",
823 action="store_false",
824 default=False,
825 help="Do not mount the chroot on a loopback image; "
826 "instead, create it directly in a directory.",
827 )
828 parser.add_argument(
829 "--use-image",
830 dest="use_image",
831 action="store_true",
832 default=False,
833 help="Mount the chroot on a loopback image "
834 "instead of creating it directly in a directory.",
835 )
Brian Harringb938c782012-02-29 15:14:38 -0800836
Alex Klein1699fab2022-09-08 08:46:06 -0600837 parser.add_argument(
838 "--chrome-root",
839 "--chrome_root",
840 type="path",
841 help="Mount this chrome root into the SDK chroot",
842 )
843 parser.add_argument(
844 "--chrome_root_mount",
845 type="path",
846 help="Mount chrome into this path inside SDK chroot",
847 )
848 parser.add_argument(
849 "--nousepkg",
850 action="store_true",
851 default=False,
852 help="Do not use binary packages when creating a chroot.",
853 )
854 parser.add_argument(
855 "-u",
856 "--url",
857 dest="sdk_url",
858 help="Use sdk tarball located at this url. Use file:// "
859 "for local files.",
860 )
861 parser.add_argument(
862 "--sdk-version",
863 help=(
864 "Use this sdk version. For prebuilt, current is %r"
865 ", for bootstrapping it is %r."
866 % (sdk_latest_version, bootstrap_latest_version)
867 ),
868 )
869 parser.add_argument(
Mike Frysinger85824562023-01-31 02:40:43 -0500870 "--goma-dir",
Alex Klein1699fab2022-09-08 08:46:06 -0600871 "--goma_dir",
872 type="path",
873 help="Goma installed directory to mount into the chroot.",
874 )
875 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600876 "--reclient-dir",
877 type="path",
878 help="Reclient installed directory to mount into the chroot.",
879 )
880 parser.add_argument(
881 "--reproxy-cfg-file",
882 type="path",
883 help="Config file for re-client's reproxy used for remoteexec.",
884 )
885 parser.add_argument(
886 "--skip-chroot-upgrade",
887 dest="chroot_upgrade",
888 action="store_false",
889 default=True,
Alex Kleinef517832023-01-13 12:06:51 -0700890 help="Skip automatic SDK and toolchain upgrade when entering the "
891 "chroot. Never guaranteed to work, especially as ToT moves forward.",
Alex Klein1699fab2022-09-08 08:46:06 -0600892 )
Yong Hong84ba9172018-02-07 01:37:42 +0800893
Alex Klein1699fab2022-09-08 08:46:06 -0600894 # Use type=str instead of type='path' to prevent the given path from being
Alex Kleinef517832023-01-13 12:06:51 -0700895 # transferred to absolute path automatically.
Alex Klein1699fab2022-09-08 08:46:06 -0600896 parser.add_argument(
897 "--working-dir",
898 type=str,
899 help="Run the command in specific working directory in "
900 "chroot. If the given directory is a relative "
901 "path, this program will transfer the path to "
902 "the corresponding one inside chroot.",
903 )
Yong Hong84ba9172018-02-07 01:37:42 +0800904
Alex Klein1699fab2022-09-08 08:46:06 -0600905 parser.add_argument("commands", nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500906
Alex Klein1699fab2022-09-08 08:46:06 -0600907 # Commands.
908 group = parser.add_argument_group("Commands")
Mike Frysinger79024a32021-04-05 03:28:35 -0400909 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600910 "--enter",
911 action="store_true",
912 default=False,
913 help="Enter the SDK chroot. Implies --create.",
914 )
Mike Frysinger79024a32021-04-05 03:28:35 -0400915 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600916 "--create",
917 action="store_true",
918 default=False,
Alex Klein22690a12022-11-17 10:56:09 -0700919 help="Create the chroot only if it does not already exist. Downloads "
920 "the SDK only if needed, even if --download explicitly passed.",
Alex Klein1699fab2022-09-08 08:46:06 -0600921 )
922 group.add_argument(
923 "--bootstrap",
924 action="store_true",
925 default=False,
926 help="Build everything from scratch, including the sdk. "
927 "Use this only if you need to validate a change "
928 "that affects SDK creation itself (toolchain and "
929 "build are typically the only folk who need this). "
930 "Note this will quite heavily slow down the build. "
931 "This option implies --create --nousepkg.",
932 )
933 group.add_argument(
934 "-r",
935 "--replace",
936 action="store_true",
937 default=False,
938 help="Replace an existing SDK chroot. Basically an alias "
939 "for --delete --create.",
940 )
941 group.add_argument(
942 "--delete",
943 action="store_true",
944 default=False,
945 help="Delete the current SDK chroot if it exists.",
946 )
947 group.add_argument(
948 "--force",
949 action="store_true",
950 default=False,
951 help="Force unmount/delete of the current SDK chroot even if "
952 "obtaining the write lock fails.",
953 )
954 group.add_argument(
955 "--unmount",
956 action="store_true",
957 default=False,
958 help="Unmount and clean up devices associated with the "
959 "SDK chroot if it exists. This does not delete the "
960 "backing image file, so the same chroot can be later "
961 "re-mounted for reuse. To fully delete the chroot, use "
962 "--delete. This is primarily useful for working on "
963 "cros_sdk or the chroot setup; you should not need it "
964 "under normal circumstances.",
965 )
966 group.add_argument(
967 "--download",
968 action="store_true",
969 default=False,
970 help="Download the sdk.",
971 )
972 group.add_argument(
973 "--snapshot-create",
974 metavar="SNAPSHOT_NAME",
975 help="Create a snapshot of the chroot. Requires that the chroot was "
976 "created without the --nouse-image option.",
977 )
978 group.add_argument(
979 "--snapshot-restore",
980 metavar="SNAPSHOT_NAME",
981 help="Restore the chroot to a previously created snapshot.",
982 )
983 group.add_argument(
984 "--snapshot-delete",
985 metavar="SNAPSHOT_NAME",
986 help="Delete a previously created snapshot. Deleting a snapshot that "
987 "does not exist is not an error.",
988 )
989 group.add_argument(
990 "--snapshot-list",
991 action="store_true",
992 default=False,
993 help="List existing snapshots of the chroot and exit.",
994 )
995 commands = group
Mike Frysinger80dfce92014-04-21 10:58:53 -0400996
Alex Klein1699fab2022-09-08 08:46:06 -0600997 # Namespace options.
998 group = parser.add_argument_group("Namespaces")
999 group.add_argument(
1000 "--proxy-sim",
1001 action="store_true",
1002 default=False,
1003 help="Simulate a restrictive network requiring an outbound" " proxy.",
1004 )
1005 for ns, default in (("pid", True), ("net", None)):
1006 group.add_argument(
1007 f"--ns-{ns}",
1008 default=default,
1009 action="store_true",
1010 help=f"Create a new {ns} namespace.",
1011 )
1012 group.add_argument(
1013 f"--no-ns-{ns}",
1014 dest=f"ns_{ns}",
1015 action="store_false",
1016 help=f"Do not create a new {ns} namespace.",
1017 )
Mike Frysinger5f5c70b2022-07-19 12:55:21 -04001018
Alex Klein1699fab2022-09-08 08:46:06 -06001019 # Debug options.
1020 group = parser.debug_group
1021 group.add_argument(
1022 "--strace",
1023 action="store_true",
1024 help="Run cros_sdk through strace after re-exec via sudo",
1025 )
1026 group.add_argument(
1027 "--strace-arguments",
1028 default="",
1029 help="Extra strace options (shell quoting permitted)",
1030 )
Mike Frysinger34db8692013-11-11 14:54:08 -05001031
Alex Klein1699fab2022-09-08 08:46:06 -06001032 # Internal options.
1033 group = parser.add_argument_group(
1034 "Internal Chromium OS Build Team Options",
1035 "Caution: these are for meant for the Chromium OS build team only",
1036 )
1037 group.add_argument(
1038 "--buildbot-log-version",
1039 default=False,
1040 action="store_true",
1041 help="Log SDK version for buildbot consumption",
1042 )
1043
1044 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -05001045
1046
1047def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001048 # Turn on strict sudo checks.
1049 cros_build_lib.STRICT_SUDO = True
1050 conf = key_value_store.LoadFile(
1051 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
1052 ignore_missing=True,
1053 )
1054 sdk_latest_version = conf.get("SDK_LATEST_VERSION", "<unknown>")
1055 bootstrap_frozen_version = conf.get("BOOTSTRAP_FROZEN_VERSION", "<unknown>")
Manoj Gupta55a63092019-06-13 11:47:13 -07001056
Alex Klein1699fab2022-09-08 08:46:06 -06001057 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
1058 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
1059 bootstrap_latest_version = (
1060 sdk_latest_version
1061 if bootstrap_frozen_version == "<unknown>"
1062 else bootstrap_frozen_version
1063 )
1064 parser, commands = _CreateParser(
1065 sdk_latest_version, bootstrap_latest_version
1066 )
1067 options = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -08001068
Alex Klein1699fab2022-09-08 08:46:06 -06001069 # Some basic checks first, before we ask for sudo credentials.
1070 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -08001071
Alex Klein1699fab2022-09-08 08:46:06 -06001072 host = os.uname()[4]
1073 if host != "x86_64":
1074 cros_build_lib.Die(
1075 "cros_sdk is currently only supported on x86_64; you're running"
1076 " %s. Please find a x86_64 machine." % (host,)
1077 )
Brian Harring1790ac42012-09-23 08:53:33 -07001078
Mike Frysinger9a5124e2023-01-26 17:16:44 -05001079 goma = goma_lib.Goma(options.goma_dir) if options.goma_dir else None
1080
Alex Klein1699fab2022-09-08 08:46:06 -06001081 # Merge the outside PATH setting if we re-execed ourselves.
1082 if "CHROMEOS_SUDO_PATH" in os.environ:
1083 os.environ["PATH"] = "%s:%s" % (
1084 os.environ.pop("CHROMEOS_SUDO_PATH"),
1085 os.environ["PATH"],
1086 )
Mike Frysinger2bda4d12020-07-14 11:15:49 -04001087
Alex Klein1699fab2022-09-08 08:46:06 -06001088 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
1089 if options.proxy_sim:
1090 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
1091 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -08001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 if (
1094 sdk_latest_version == "<unknown>"
1095 or bootstrap_latest_version == "<unknown>"
1096 ):
1097 cros_build_lib.Die(
1098 "No SDK version was found. "
1099 "Are you in a Chromium source tree instead of Chromium OS?\n\n"
1100 "Please change to a directory inside your Chromium OS source tree\n"
1101 "and retry. If you need to setup a Chromium OS source tree, see\n"
1102 " https://dev.chromium.org/chromium-os/developer-guide"
1103 )
Benjamin Gordon040a1162017-06-29 13:44:47 -06001104
Alex Klein1699fab2022-09-08 08:46:06 -06001105 any_snapshot_operation = (
1106 options.snapshot_create
1107 or options.snapshot_restore
1108 or options.snapshot_delete
1109 or options.snapshot_list
1110 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001111
Alex Klein1699fab2022-09-08 08:46:06 -06001112 if (
1113 options.snapshot_delete
1114 and options.snapshot_delete == options.snapshot_restore
1115 ):
1116 parser.error(
1117 "Cannot --snapshot_delete the same snapshot you are "
1118 "restoring with --snapshot_restore."
1119 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001120
Alex Klein1699fab2022-09-08 08:46:06 -06001121 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -08001122
Alex Klein1699fab2022-09-08 08:46:06 -06001123 lock_path = os.path.dirname(options.chroot)
1124 lock_path = os.path.join(
1125 lock_path, ".%s_lock" % os.path.basename(options.chroot).lstrip(".")
1126 )
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001127
Alex Klein1699fab2022-09-08 08:46:06 -06001128 # Expand out the aliases...
1129 if options.replace:
1130 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001131
Alex Klein1699fab2022-09-08 08:46:06 -06001132 if options.bootstrap:
1133 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001134
Alex Klein1699fab2022-09-08 08:46:06 -06001135 # If a command is not given, default to enter.
1136 # pylint: disable=protected-access
1137 # This _group_actions access sucks, but upstream decided to not include an
1138 # alternative to optparse's option_list, and this is what they recommend.
1139 options.enter |= not any(
1140 getattr(options, x.dest) for x in commands._group_actions
1141 )
1142 # pylint: enable=protected-access
Mike Frysinger114a7b92023-01-26 19:08:57 -05001143 options.enter |= bool(options.commands)
Brian Harring218e13c2012-10-10 16:21:26 -07001144
Alex Klein1699fab2022-09-08 08:46:06 -06001145 if (
1146 options.delete
1147 and not options.create
1148 and (options.enter or any_snapshot_operation)
1149 ):
1150 parser.error(
1151 "Trying to enter or snapshot the chroot when --delete "
1152 "was specified makes no sense."
1153 )
Brian Harring218e13c2012-10-10 16:21:26 -07001154
Alex Klein1699fab2022-09-08 08:46:06 -06001155 if options.unmount and (
1156 options.create or options.enter or any_snapshot_operation
1157 ):
1158 parser.error("--unmount cannot be specified with other chroot actions.")
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001159
Alex Klein1699fab2022-09-08 08:46:06 -06001160 if options.working_dir is not None and not os.path.isabs(
1161 options.working_dir
1162 ):
1163 options.working_dir = path_util.ToChrootPath(options.working_dir)
Yong Hong84ba9172018-02-07 01:37:42 +08001164
Alex Klein1699fab2022-09-08 08:46:06 -06001165 # If there is an existing chroot image and we're not removing it then force
1166 # use_image on. This ensures that people don't have to remember to pass
Alex Kleinef517832023-01-13 12:06:51 -07001167 # --use-image after a reboot to avoid losing access to their existing
1168 # chroot.
Alex Klein1699fab2022-09-08 08:46:06 -06001169 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1170 img_path = _ImageFileForChroot(options.chroot)
1171 if (
1172 not options.use_image
1173 and not options.delete
1174 and not options.unmount
1175 and os.path.exists(img_path)
1176 ):
1177 if chroot_exists:
1178 # If the chroot is already populated, make sure it has something
1179 # mounted on it before we assume it came from an image.
1180 cmd = ["mountpoint", "-q", options.chroot]
1181 if cros_build_lib.dbg_run(cmd, check=False).returncode == 0:
1182 options.use_image = True
Benjamin Gordon832a4412020-12-08 10:39:16 -07001183
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001184 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001185 logging.notice(
1186 "Existing chroot image %s found. Forcing --use-image on.",
1187 img_path,
1188 )
1189 options.use_image = True
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001190
Alex Klein1699fab2022-09-08 08:46:06 -06001191 if any_snapshot_operation and not options.use_image:
1192 if os.path.exists(img_path):
1193 options.use_image = True
1194 else:
1195 cros_build_lib.Die(
1196 "Snapshot operations are not compatible with " "--nouse-image."
1197 )
Mike Frysingerc60141b2023-01-27 00:57:15 -05001198 if options.use_image:
1199 logging.warning("--use-image is deprecated and will be removed soon.")
1200 logging.warning("Please migrate, or create a new one with --delete.")
1201 logging.warning("See http://b/266878468 for details.")
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001202
Alex Klein1699fab2022-09-08 08:46:06 -06001203 # Discern if we need to create the chroot.
1204 if (
1205 options.use_image
1206 and not chroot_exists
1207 and not options.delete
1208 and not options.unmount
1209 and not missing_image_tools
1210 and os.path.exists(img_path)
1211 ):
1212 # Try to re-mount an existing image in case the user has rebooted.
1213 with locking.FileLock(lock_path, "chroot lock") as lock:
1214 logging.debug("Checking if existing chroot image can be mounted.")
1215 lock.write_lock()
1216 cros_sdk_lib.MountChroot(options.chroot, create=False)
1217 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1218 if chroot_exists:
1219 logging.notice("Mounted existing image %s on chroot", img_path)
1220
1221 # Finally, flip create if necessary.
1222 if options.enter or options.snapshot_create:
1223 options.create |= not chroot_exists
1224
1225 # Make sure we will download if we plan to create.
1226 options.download |= options.create
1227
Mike Frysinger6bbd3e92023-01-27 00:05:02 -05001228 options.Freeze()
1229
Mike Frysinger253c8392023-01-27 01:12:21 -05001230 remoteexec = (
1231 remoteexec_util.Remoteexec(
1232 options.reclient_dir, options.reproxy_cfg_file
1233 )
1234 if (options.reclient_dir and options.reproxy_cfg_file)
1235 else None
1236 )
1237
Mike Frysingerec32bea2023-01-27 01:20:48 -05001238 chroot = chroot_lib.Chroot(
1239 path=options.chroot,
1240 cache_dir=options.cache_dir,
1241 chrome_root=options.chrome_root,
1242 goma=goma,
1243 remoteexec=remoteexec,
1244 )
1245
Alex Kleinef517832023-01-13 12:06:51 -07001246 # Anything that needs to manipulate the main chroot mount or communicate
1247 # with LVM needs to be done here before we enter the new namespaces.
Alex Klein1699fab2022-09-08 08:46:06 -06001248
1249 # If deleting, do it regardless of the use_image flag so that a
1250 # previously-created loopback chroot can also be cleaned up.
1251 if options.delete:
1252 # Set a timeout of 300 seconds when getting the lock.
1253 with locking.FileLock(
1254 lock_path, "chroot lock", blocking_timeout=300
1255 ) as lock:
1256 try:
1257 lock.write_lock()
1258 except timeout_util.TimeoutError as e:
1259 logging.error(
1260 "Acquiring write_lock on %s failed: %s", lock_path, e
1261 )
1262 if not options.force:
1263 cros_build_lib.Die(
1264 "Exiting; use --force to continue w/o lock."
1265 )
1266 else:
1267 logging.warning(
1268 "cros_sdk was invoked with force option, continuing."
1269 )
1270 logging.notice("Deleting chroot.")
Mike Frysingerec32bea2023-01-27 01:20:48 -05001271 cros_sdk_lib.CleanupChrootMount(chroot.path, delete=True)
Alex Klein1699fab2022-09-08 08:46:06 -06001272
Alex Kleinef517832023-01-13 12:06:51 -07001273 # If cleanup was requested, we have to do it while we're still in the
1274 # original namespace. Since cleaning up the mount will interfere with any
1275 # other commands, we exit here. The check above should have made sure that
1276 # no other action was requested, anyway.
Alex Klein1699fab2022-09-08 08:46:06 -06001277 if options.unmount:
1278 # Set a timeout of 300 seconds when getting the lock.
1279 with locking.FileLock(
1280 lock_path, "chroot lock", blocking_timeout=300
1281 ) as lock:
1282 try:
1283 lock.write_lock()
1284 except timeout_util.TimeoutError as e:
1285 logging.error(
1286 "Acquiring write_lock on %s failed: %s", lock_path, e
1287 )
1288 logging.warning(
Alex Kleinef517832023-01-13 12:06:51 -07001289 "Continuing with CleanupChroot(%s), which will umount the "
1290 "tree.",
Mike Frysingerec32bea2023-01-27 01:20:48 -05001291 chroot.path,
Alex Klein1699fab2022-09-08 08:46:06 -06001292 )
Alex Kleinef517832023-01-13 12:06:51 -07001293 # We can call CleanupChroot (which calls
1294 # cros_sdk_lib.CleanupChrootMount) even if we don't get the lock
1295 # because it will attempt to unmount the tree and will print
1296 # diagnostic information from 'fuser', 'lsof', and 'ps'.
Mike Frysingerec32bea2023-01-27 01:20:48 -05001297 cros_sdk_lib.CleanupChrootMount(chroot.path, delete=False)
Alex Klein1699fab2022-09-08 08:46:06 -06001298 sys.exit(0)
1299
1300 # Make sure the main chroot mount is visible. Contents will be filled in
1301 # below if needed.
1302 if options.create and options.use_image:
1303 if missing_image_tools:
1304 raise SystemExit(
1305 """The tool(s) %s were not found.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001306Please make sure the lvm2 and thin-provisioning-tools packages
1307are installed on your host.
1308Example(ubuntu):
1309 sudo apt-get install lvm2 thin-provisioning-tools
1310
1311If you want to run without lvm2, pass --nouse-image (chroot
Alex Klein1699fab2022-09-08 08:46:06 -06001312snapshots will be unavailable)."""
1313 % ", ".join(missing_image_tools)
1314 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001315
Alex Klein1699fab2022-09-08 08:46:06 -06001316 logging.debug("Making sure chroot image is mounted.")
1317 with locking.FileLock(lock_path, "chroot lock") as lock:
1318 lock.write_lock()
Mike Frysingerec32bea2023-01-27 01:20:48 -05001319 if not cros_sdk_lib.MountChroot(chroot.path, create=True):
Alex Klein1699fab2022-09-08 08:46:06 -06001320 cros_build_lib.Die(
1321 "Unable to mount %s on chroot",
Mike Frysingerec32bea2023-01-27 01:20:48 -05001322 _ImageFileForChroot(chroot.path),
Alex Klein1699fab2022-09-08 08:46:06 -06001323 )
1324 logging.notice(
Mike Frysingerec32bea2023-01-27 01:20:48 -05001325 "Mounted %s on chroot", _ImageFileForChroot(chroot.path)
Alex Klein1699fab2022-09-08 08:46:06 -06001326 )
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001327
Alex Klein1699fab2022-09-08 08:46:06 -06001328 # Snapshot operations will always need the VG/LV, but other actions won't.
1329 if any_snapshot_operation:
1330 with locking.FileLock(lock_path, "chroot lock") as lock:
1331 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Mike Frysingerec32bea2023-01-27 01:20:48 -05001332 chroot.path
Alex Klein1699fab2022-09-08 08:46:06 -06001333 )
1334 if not chroot_vg or not chroot_lv:
1335 cros_build_lib.Die(
Mike Frysingerec32bea2023-01-27 01:20:48 -05001336 "Unable to find VG/LV for chroot %s", chroot.path
Alex Klein1699fab2022-09-08 08:46:06 -06001337 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001338
Alex Kleinef517832023-01-13 12:06:51 -07001339 # Delete snapshot before creating a new one. This allows the user to
1340 # throw out old state, create a new snapshot, and enter the chroot
1341 # in a single call to cros_sdk. Since restore involves deleting,
1342 # also do it before creating.
Alex Klein1699fab2022-09-08 08:46:06 -06001343 if options.snapshot_restore:
1344 lock.write_lock()
1345 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1346 if options.snapshot_restore not in valid_snapshots:
1347 cros_build_lib.Die(
Alex Kleinef517832023-01-13 12:06:51 -07001348 "%s is not a valid snapshot to restore to. "
1349 "Valid snapshots: %s",
Alex Klein1699fab2022-09-08 08:46:06 -06001350 options.snapshot_restore,
1351 ", ".join(valid_snapshots),
1352 )
Mike Frysingerec32bea2023-01-27 01:20:48 -05001353 osutils.UmountTree(chroot.path)
Alex Klein1699fab2022-09-08 08:46:06 -06001354 if not RestoreChrootSnapshot(
1355 options.snapshot_restore, chroot_vg, chroot_lv
1356 ):
1357 cros_build_lib.Die("Unable to restore chroot to snapshot.")
Mike Frysingerec32bea2023-01-27 01:20:48 -05001358 if not cros_sdk_lib.MountChroot(chroot.path, create=False):
Alex Klein1699fab2022-09-08 08:46:06 -06001359 cros_build_lib.Die(
1360 "Unable to mount restored snapshot onto chroot."
1361 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001362
Alex Kleinef517832023-01-13 12:06:51 -07001363 # Use a read lock for snapshot delete and create even though they
1364 # modify the filesystem, because they don't modify the mounted
1365 # chroot itself. The underlying LVM commands take their own locks,
1366 # so conflicting concurrent operations here may crash cros_sdk, but
1367 # won't corrupt the chroot image. This tradeoff seems worth it to
1368 # allow snapshot operations on chroots that have a process inside.
Alex Klein1699fab2022-09-08 08:46:06 -06001369 if options.snapshot_delete:
1370 lock.read_lock()
1371 DeleteChrootSnapshot(
1372 options.snapshot_delete, chroot_vg, chroot_lv
1373 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001374
Alex Klein1699fab2022-09-08 08:46:06 -06001375 if options.snapshot_create:
1376 lock.read_lock()
1377 if not CreateChrootSnapshot(
1378 options.snapshot_create, chroot_vg, chroot_lv
1379 ):
1380 cros_build_lib.Die("Unable to create snapshot.")
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001381
Mike Frysingerec32bea2023-01-27 01:20:48 -05001382 img_path = _ImageFileForChroot(chroot.path)
Alex Klein1699fab2022-09-08 08:46:06 -06001383 if (
1384 options.use_image
Mike Frysingerec32bea2023-01-27 01:20:48 -05001385 and os.path.exists(chroot.path)
Alex Klein1699fab2022-09-08 08:46:06 -06001386 and os.path.exists(img_path)
1387 ):
1388 img_stat = os.stat(img_path)
1389 img_used_bytes = img_stat.st_blocks * 512
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001390
Mike Frysingerec32bea2023-01-27 01:20:48 -05001391 mount_stat = os.statvfs(chroot.path)
Alex Klein1699fab2022-09-08 08:46:06 -06001392 mount_used_bytes = mount_stat.f_frsize * (
1393 mount_stat.f_blocks - mount_stat.f_bfree
1394 )
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001395
Alex Klein1699fab2022-09-08 08:46:06 -06001396 extra_gbs = (img_used_bytes - mount_used_bytes) // 2**30
1397 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1398 logging.notice(
1399 "%s is using %s GiB more than needed. Running "
1400 "fstrim in background.",
1401 img_path,
1402 extra_gbs,
1403 )
1404 pid = os.fork()
1405 if pid == 0:
1406 try:
1407 # Directly call Popen to run fstrim concurrently.
Mike Frysingerec32bea2023-01-27 01:20:48 -05001408 cmd = ["fstrim", chroot.path]
Alex Klein1699fab2022-09-08 08:46:06 -06001409 subprocess.Popen(cmd, close_fds=True, shell=False)
1410 except subprocess.SubprocessError as e:
1411 logging.warning(
1412 "Running fstrim failed. Consider running fstrim on "
1413 "your chroot manually.\n%s",
1414 e,
1415 )
1416 os._exit(0) # pylint: disable=protected-access
1417 os.waitpid(pid, 0)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001418
Alex Kleinef517832023-01-13 12:06:51 -07001419 # Enter a new set of namespaces. Everything after here cannot directly
1420 # affect the hosts's mounts or alter LVM volumes.
Alex Klein1699fab2022-09-08 08:46:06 -06001421 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001422
Alex Klein1699fab2022-09-08 08:46:06 -06001423 if options.snapshot_list:
1424 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1425 print(snap)
1426 sys.exit(0)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001427
Alex Klein1699fab2022-09-08 08:46:06 -06001428 if not options.sdk_version:
1429 sdk_version = (
1430 bootstrap_latest_version
1431 if options.bootstrap
1432 else sdk_latest_version
1433 )
Yong Hong4e29b622018-02-05 14:31:10 +08001434 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001435 sdk_version = options.sdk_version
1436 if options.buildbot_log_version:
1437 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001438
Alex Klein1699fab2022-09-08 08:46:06 -06001439 # Based on selections, determine the tarball to fetch.
Alex Klein22690a12022-11-17 10:56:09 -07001440 urls = []
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001441 if options.download:
Alex Klein1699fab2022-09-08 08:46:06 -06001442 if options.sdk_url:
1443 urls = [options.sdk_url]
1444 else:
1445 urls = GetArchStageTarballs(sdk_version)
Brian Harring218e13c2012-10-10 16:21:26 -07001446
Alex Klein1699fab2022-09-08 08:46:06 -06001447 with locking.FileLock(lock_path, "chroot lock") as lock:
1448 if options.proxy_sim:
1449 _ProxySimSetup(options)
Brian Harring1790ac42012-09-23 08:53:33 -07001450
Mike Frysingerec32bea2023-01-27 01:20:48 -05001451 sdk_cache = os.path.join(chroot.cache_dir, "sdks")
1452 distfiles_cache = os.path.join(chroot.cache_dir, "distfiles")
1453 osutils.SafeMakedirsNonRoot(chroot.cache_dir)
Dan Callaghanada9e032023-01-30 14:28:46 +11001454 osutils.SafeMakedirsNonRoot(options.out_dir)
Alex Klein1699fab2022-09-08 08:46:06 -06001455
1456 for target in (sdk_cache, distfiles_cache):
1457 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
1458 if not os.path.exists(src):
1459 osutils.SafeMakedirsNonRoot(target)
1460 continue
1461 lock.write_lock(
1462 "Upgrade to %r needed but chroot is locked; please exit "
1463 "all instances so this upgrade can finish." % src
1464 )
1465 if not os.path.exists(src):
Alex Kleinef517832023-01-13 12:06:51 -07001466 # Note that while waiting for the write lock, src may've
1467 # vanished; it's a rare race during the upgrade process that's a
1468 # byproduct of us avoiding taking a write lock to do the src
1469 # check. If we took a write lock for that check, it would
1470 # effectively limit all cros_sdk for a chroot to a single
1471 # instance.
Alex Klein1699fab2022-09-08 08:46:06 -06001472 osutils.SafeMakedirsNonRoot(target)
1473 elif not os.path.exists(target):
1474 # Upgrade occurred, but a reversion, or something whacky
1475 # occurred writing to the old location. Wipe and continue.
1476 os.rename(src, target)
1477 else:
1478 # Upgrade occurred once already, but either a reversion or
1479 # some before/after separate cros_sdk usage is at play.
1480 # Wipe and continue.
1481 osutils.RmDir(src)
1482
Alex Klein1699fab2022-09-08 08:46:06 -06001483 mounted = False
1484 if options.create:
1485 lock.write_lock()
Alex Kleinef517832023-01-13 12:06:51 -07001486 # Recheck if the chroot is set up here before creating to make sure
1487 # we account for whatever the various delete/unmount/remount steps
1488 # above have done.
Mike Frysingerec32bea2023-01-27 01:20:48 -05001489 if cros_sdk_lib.IsChrootReady(chroot.path):
Alex Klein1699fab2022-09-08 08:46:06 -06001490 logging.debug("Chroot already exists. Skipping creation.")
1491 else:
Alex Klein22690a12022-11-17 10:56:09 -07001492 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -06001493 cros_sdk_lib.CreateChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -05001494 Path(chroot.path),
Alex Klein1699fab2022-09-08 08:46:06 -06001495 Path(sdk_tarball),
Dan Callaghanada9e032023-01-30 14:28:46 +11001496 options.out_dir,
Mike Frysingerec32bea2023-01-27 01:20:48 -05001497 Path(chroot.cache_dir),
Alex Klein1699fab2022-09-08 08:46:06 -06001498 usepkg=not options.bootstrap and not options.nousepkg,
1499 chroot_upgrade=options.chroot_upgrade,
1500 )
1501 mounted = True
Alex Klein22690a12022-11-17 10:56:09 -07001502 elif options.download:
1503 # Allow downloading only.
1504 lock.write_lock()
1505 FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -06001506
1507 if options.enter:
1508 lock.read_lock()
1509 if not mounted:
Dan Callaghanada9e032023-01-30 14:28:46 +11001510 cros_sdk_lib.MountChrootPaths(chroot.path, options.out_dir)
Alex Klein1699fab2022-09-08 08:46:06 -06001511 ret = EnterChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -05001512 chroot,
Alex Klein1699fab2022-09-08 08:46:06 -06001513 options.chrome_root_mount,
Alex Klein1699fab2022-09-08 08:46:06 -06001514 options.working_dir,
Mike Frysinger114a7b92023-01-26 19:08:57 -05001515 options.commands,
Alex Klein1699fab2022-09-08 08:46:06 -06001516 )
1517 sys.exit(ret.returncode)