blob: c03c4af6b1e659978250954be5b87f24847f4200 [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 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:
Alex Kleinef517832023-01-13 12:06:51 -0700114 storage_dir: Path where to save the tarball.
115 urls: List of URLs to try to download. Download will stop on first
116 success.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200117
Alex Klein1699fab2022-09-08 08:46:06 -0600118 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700119 Full path to the downloaded file.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700122 ValueError: None of the URLs worked.
Alex Klein1699fab2022-09-08 08:46:06 -0600123 """
124 # Note we track content length ourselves since certain versions of curl
125 # fail if asked to resume a complete file.
126 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
127 status_re = re.compile(rb"^HTTP/[0-9]+(\.[0-9]+)? 200")
128 # pylint: disable=undefined-loop-variable
129 for url in urls:
130 logging.notice("Downloading tarball %s ...", urls[0].rsplit("/", 1)[-1])
131 parsed = urllib.parse.urlparse(url)
132 tarball_name = os.path.basename(parsed.path)
133 if parsed.scheme in ("", "file"):
134 if os.path.exists(parsed.path):
135 return parsed.path
136 continue
137 content_length = 0
138 logging.debug("Attempting download from %s", url)
139 result = retry_util.RunCurl(
140 ["-I", url],
141 print_cmd=False,
142 debug_level=logging.NOTICE,
143 capture_output=True,
144 )
145 successful = False
146 for header in result.stdout.splitlines():
147 # We must walk the output to find the 200 code for use cases where
148 # a proxy is involved and may have pushed down the actual header.
149 if status_re.match(header):
150 successful = True
151 elif header.lower().startswith(b"content-length:"):
152 content_length = int(header.split(b":", 1)[-1].strip())
153 if successful:
154 break
Brian Harring1790ac42012-09-23 08:53:33 -0700155 if successful:
Alex Klein1699fab2022-09-08 08:46:06 -0600156 break
157 else:
158 raise ValueError("No valid URLs found!")
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 tarball_dest = os.path.join(storage_dir, tarball_name)
161 current_size = 0
162 if os.path.exists(tarball_dest):
163 current_size = os.path.getsize(tarball_dest)
164 if current_size > content_length:
165 osutils.SafeUnlink(tarball_dest)
166 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100167
Alex Klein1699fab2022-09-08 08:46:06 -0600168 if current_size < content_length:
169 retry_util.RunCurl(
170 [
171 "--fail",
172 "-L",
173 "-y",
174 "30",
175 "-C",
176 "-",
177 "--output",
178 tarball_dest,
179 url,
180 ],
181 print_cmd=False,
182 debug_level=logging.NOTICE,
183 )
Brian Harringb938c782012-02-29 15:14:38 -0800184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 # Cleanup old tarballs now since we've successfull fetched; only cleanup
186 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
187 # because we might have partial overlap between known prefixes.
188 for p in Path(storage_dir).glob("cros-sdk-*"):
189 if p.name == tarball_name:
190 continue
191 logging.info("Cleaning up old tarball: %s", p)
192 osutils.SafeUnlink(p)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 return tarball_dest
Brian Harringb938c782012-02-29 15:14:38 -0800195
196
Alex Klein1699fab2022-09-08 08:46:06 -0600197def EnterChroot(
198 chroot_path,
199 cache_dir,
200 chrome_root,
201 chrome_root_mount,
202 goma_dir,
203 goma_client_json,
204 reclient_dir,
205 reproxy_cfg_file,
206 working_dir,
207 additional_args,
208):
209 """Enters an existing SDK chroot"""
210 st = os.statvfs(os.path.join(chroot_path, "usr", "bin", "sudo"))
211 if st.f_flag & os.ST_NOSUID:
212 cros_build_lib.Die("chroot cannot be in a nosuid mount")
Mike Frysingere5456972013-06-13 00:07:23 -0400213
Alex Klein1699fab2022-09-08 08:46:06 -0600214 cmd = ENTER_CHROOT + ["--chroot", chroot_path, "--cache_dir", cache_dir]
215 if chrome_root:
216 cmd.extend(["--chrome_root", chrome_root])
217 if chrome_root_mount:
218 cmd.extend(["--chrome_root_mount", chrome_root_mount])
219 if goma_dir:
220 cmd.extend(["--goma_dir", goma_dir])
221 if goma_client_json:
222 cmd.extend(["--goma_client_json", goma_client_json])
223 if reclient_dir:
224 cmd.extend(["--reclient_dir", reclient_dir])
225 if reproxy_cfg_file:
226 cmd.extend(["--reproxy_cfg_file", reproxy_cfg_file])
227 if working_dir is not None:
228 cmd.extend(["--working_dir", working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700229
Alex Klein1699fab2022-09-08 08:46:06 -0600230 if additional_args:
231 cmd.append("--")
232 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700233
Alex Klein1699fab2022-09-08 08:46:06 -0600234 if "CHROMEOS_SUDO_RLIMITS" in os.environ:
235 _SetRlimits(os.environ.pop("CHROMEOS_SUDO_RLIMITS"))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 # Some systems set the soft limit too low. Bump it up to the hard limit.
238 # We don't override the hard limit because it's something the admins put
239 # in place and we want to respect such configs. http://b/234353695
240 soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
241 if soft != resource.RLIM_INFINITY and soft < 4096:
242 if soft < hard or hard == resource.RLIM_INFINITY:
243 resource.setrlimit(resource.RLIMIT_NPROC, (hard, hard))
Mike Frysingerebb23fc2022-06-06 21:17:39 -0400244
Alex Klein1699fab2022-09-08 08:46:06 -0600245 # ThinLTO opens lots of files at the same time.
246 # Set rlimit and vm.max_map_count to accommodate this.
247 file_limit = 262144
248 soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
249 resource.setrlimit(
250 resource.RLIMIT_NOFILE, (max(soft, file_limit), max(hard, file_limit))
251 )
252 max_map_count = int(osutils.ReadFile("/proc/sys/vm/max_map_count"))
253 if max_map_count < file_limit:
254 logging.notice(
255 "Raising vm.max_map_count from %s to %s", max_map_count, file_limit
256 )
257 osutils.WriteFile("/proc/sys/vm/max_map_count", str(file_limit))
258 return cros_build_lib.dbg_run(cmd, check=False)
Brian Harringb938c782012-02-29 15:14:38 -0800259
260
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600261def _ImageFileForChroot(chroot):
Alex Klein1699fab2022-09-08 08:46:06 -0600262 """Find the image file that should be associated with |chroot|.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 This function does not check if the image exists; it simply returns the
265 filename that would be used.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700268 chroot: Path to the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700271 Path to an image file that would be associated with chroot.
Alex Klein1699fab2022-09-08 08:46:06 -0600272 """
273 return chroot.rstrip("/") + ".img"
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600274
275
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600276def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600277 """Create a snapshot for the specified chroot VG/LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600278
Alex Klein1699fab2022-09-08 08:46:06 -0600279 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700280 snapshot_name: The name of the new snapshot.
281 chroot_vg: The name of the VG containing the origin LV.
282 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700285 True if the snapshot was created, or False if a snapshot with the same
286 name already exists.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700289 SystemExit: The lvcreate command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600290 """
291 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
292 logging.error(
293 "Cannot create snapshot %s: A volume with that name already "
294 "exists.",
295 snapshot_name,
296 )
297 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 cmd = [
300 "lvcreate",
301 "-s",
302 "--name",
303 snapshot_name,
304 "%s/%s" % (chroot_vg, chroot_lv),
305 ]
306 try:
307 logging.notice(
308 "Creating snapshot %s from %s in VG %s.",
309 snapshot_name,
310 chroot_lv,
311 chroot_vg,
312 )
313 cros_build_lib.dbg_run(cmd, capture_output=True)
314 return True
315 except cros_build_lib.RunCommandError as e:
316 cros_build_lib.Die("Creating snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600317
318
319def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600320 """Delete the named snapshot from the specified chroot VG.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600321
Alex Klein1699fab2022-09-08 08:46:06 -0600322 If the requested snapshot is not found, nothing happens. The main chroot LV
323 and internal thinpool LV cannot be deleted with this function.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600324
Alex Klein1699fab2022-09-08 08:46:06 -0600325 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700326 snapshot_name: The name of the snapshot to delete.
327 chroot_vg: The name of the VG containing the origin LV.
328 chroot_lv: The name of the origin LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600329
Alex Klein1699fab2022-09-08 08:46:06 -0600330 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700331 SystemExit: The lvremove command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600332 """
333 if snapshot_name in (
334 cros_sdk_lib.CHROOT_LV_NAME,
335 cros_sdk_lib.CHROOT_THINPOOL_NAME,
336 ):
337 logging.error(
338 "Cannot remove LV %s as a snapshot. Use cros_sdk --delete "
339 "if you want to remove the whole chroot.",
340 snapshot_name,
341 )
342 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
345 return
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600346
Alex Klein1699fab2022-09-08 08:46:06 -0600347 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, snapshot_name)]
348 try:
349 logging.notice(
350 "Deleting snapshot %s in VG %s.", snapshot_name, chroot_vg
351 )
352 cros_build_lib.dbg_run(cmd, capture_output=True)
353 except cros_build_lib.RunCommandError as e:
354 cros_build_lib.Die("Deleting snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600355
356
357def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600358 """Restore the chroot to an existing snapshot.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600359
Alex Klein1699fab2022-09-08 08:46:06 -0600360 This is done by renaming the original |chroot_lv| LV to a temporary name,
361 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
362 now unused LV. If an error occurs, attempts to rename the original snapshot
363 back to |chroot_lv| to leave the chroot unchanged.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 The chroot must be unmounted before calling this function, and will be left
366 unmounted after this function returns.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600367
Alex Klein1699fab2022-09-08 08:46:06 -0600368 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700369 snapshot_name: The name of the snapshot to restore. This snapshot will
370 no longer be accessible at its original name after this function
371 finishes.
372 chroot_vg: The VG containing the chroot LV and snapshot LV.
373 chroot_lv: The name of the original chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700376 True if the chroot was restored to the requested snapshot, or False if
377 the snapshot wasn't found or isn't valid.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600378
Alex Klein1699fab2022-09-08 08:46:06 -0600379 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700380 SystemExit: Any of the LVM commands failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600381 """
382 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
383 if (
384 snapshot_name
385 in (cros_sdk_lib.CHROOT_LV_NAME, cros_sdk_lib.CHROOT_THINPOOL_NAME)
386 or snapshot_name not in valid_snapshots
387 ):
388 logging.error(
389 "Chroot cannot be restored to %s. Valid snapshots: %s",
390 snapshot_name,
391 ", ".join(valid_snapshots),
392 )
393 return False
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600394
Alex Klein1699fab2022-09-08 08:46:06 -0600395 backup_chroot_name = "chroot-bak-%d" % random.randint(0, 1000)
396 cmd = ["lvrename", chroot_vg, chroot_lv, backup_chroot_name]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600397 try:
Alex Klein1699fab2022-09-08 08:46:06 -0600398 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500399 except cros_build_lib.RunCommandError as e:
Alex Klein1699fab2022-09-08 08:46:06 -0600400 cros_build_lib.Die("Restoring snapshot failed!\n%s", e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 cmd = ["lvrename", chroot_vg, snapshot_name, chroot_lv]
403 try:
404 cros_build_lib.dbg_run(cmd, capture_output=True)
405 except cros_build_lib.RunCommandError as e:
406 cmd = ["lvrename", chroot_vg, backup_chroot_name, chroot_lv]
407 try:
408 cros_build_lib.dbg_run(cmd, capture_output=True)
409 except cros_build_lib.RunCommandError as e:
410 cros_build_lib.Die(
Alex Kleinef517832023-01-13 12:06:51 -0700411 "Failed to rename %s to chroot and failed to restore %s back "
412 "to chroot!\n%s",
Alex Klein1699fab2022-09-08 08:46:06 -0600413 snapshot_name,
414 backup_chroot_name,
415 e,
416 )
417 cros_build_lib.Die(
418 "Failed to rename %s to chroot! Original chroot LV has "
419 "been restored.\n%s",
420 snapshot_name,
421 e,
422 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600423
Alex Klein1699fab2022-09-08 08:46:06 -0600424 # Some versions of LVM set snapshots to be skipped at auto-activate time.
425 # Other versions don't have this flag at all. We run lvchange to try
Alex Kleinef517832023-01-13 12:06:51 -0700426 # disabling auto-skip and activating the volume, but ignore errors. Versions
Alex Klein1699fab2022-09-08 08:46:06 -0600427 # that don't have the flag should be auto-activated.
428 chroot_lv_path = "%s/%s" % (chroot_vg, chroot_lv)
429 cmd = ["lvchange", "-kn", chroot_lv_path]
430 cros_build_lib.run(cmd, print_cmd=False, capture_output=True, check=False)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600431
Alex Klein1699fab2022-09-08 08:46:06 -0600432 # Activate the LV in case the lvchange above was needed. Activating an LV
Alex Kleinef517832023-01-13 12:06:51 -0700433 # that is already active shouldn't do anything, so this is safe to run even
434 # if the -kn wasn't needed.
Alex Klein1699fab2022-09-08 08:46:06 -0600435 cmd = ["lvchange", "-ay", chroot_lv_path]
Mike Frysinger3e8de442020-02-14 16:46:28 -0500436 cros_build_lib.dbg_run(cmd, capture_output=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600437
Alex Klein1699fab2022-09-08 08:46:06 -0600438 cmd = ["lvremove", "-f", "%s/%s" % (chroot_vg, backup_chroot_name)]
439 try:
440 cros_build_lib.dbg_run(cmd, capture_output=True)
441 except cros_build_lib.RunCommandError as e:
442 cros_build_lib.Die(
443 "Failed to remove backup LV %s/%s!\n%s",
444 chroot_vg,
445 backup_chroot_name,
446 e,
447 )
448
449 return True
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600450
451
452def ListChrootSnapshots(chroot_vg, chroot_lv):
Alex Klein1699fab2022-09-08 08:46:06 -0600453 """Return all snapshots in |chroot_vg| regardless of origin volume.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700456 chroot_vg: The name of the VG containing the chroot.
457 chroot_lv: The name of the chroot LV.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 Returns:
Alex Kleinef517832023-01-13 12:06:51 -0700460 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 Raises:
Alex Kleinef517832023-01-13 12:06:51 -0700463 SystemExit: The lvs command failed.
Alex Klein1699fab2022-09-08 08:46:06 -0600464 """
465 if not chroot_vg or not chroot_lv:
466 return []
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 cmd = [
469 "lvs",
470 "-o",
471 "lv_name,pool_lv,lv_attr",
472 "-O",
473 "lv_name",
474 "--noheadings",
475 "--separator",
476 "\t",
477 chroot_vg,
478 ]
479 try:
480 result = cros_build_lib.run(
481 cmd, print_cmd=False, stdout=True, encoding="utf-8"
482 )
483 except cros_build_lib.RunCommandError:
484 raise SystemExit("Running %r failed!" % cmd)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600485
Alex Klein1699fab2022-09-08 08:46:06 -0600486 # Once the thin origin volume has been deleted, there's no way to tell a
Alex Kleinef517832023-01-13 12:06:51 -0700487 # snapshot apart from any other volume. Since this VG is created and managed
488 # by cros_sdk, we'll assume that all volumes that share the same thin pool
489 # are valid snapshots.
Alex Klein1699fab2022-09-08 08:46:06 -0600490 snapshots = []
491 snapshot_attrs = re.compile(r"^V.....t.{2,}") # Matches a thin volume.
492 for line in result.stdout.splitlines():
493 lv_name, pool_lv, lv_attr = line.lstrip().split("\t")
494 if (
495 lv_name == chroot_lv
496 or lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME
497 or pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME
498 or not snapshot_attrs.match(lv_attr)
499 ):
500 continue
501 snapshots.append(lv_name)
502 return snapshots
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600503
504
Mike Frysinger12d055b2022-06-23 12:26:47 -0400505# The rlimits we will lookup & pass down, in order.
506RLIMITS_TO_PASS = (
507 resource.RLIMIT_AS,
508 resource.RLIMIT_CORE,
509 resource.RLIMIT_CPU,
510 resource.RLIMIT_FSIZE,
511 resource.RLIMIT_MEMLOCK,
512 resource.RLIMIT_NICE,
513 resource.RLIMIT_NOFILE,
514 resource.RLIMIT_NPROC,
515 resource.RLIMIT_RSS,
516 resource.RLIMIT_STACK,
517)
518
519
520def _GetRlimits() -> str:
Alex Klein1699fab2022-09-08 08:46:06 -0600521 """Serialize current rlimits."""
522 return str(tuple(resource.getrlimit(x) for x in RLIMITS_TO_PASS))
Mike Frysinger12d055b2022-06-23 12:26:47 -0400523
524
525def _SetRlimits(limits: str) -> None:
Alex Klein1699fab2022-09-08 08:46:06 -0600526 """Deserialize rlimits."""
527 for rlim, limit in zip(RLIMITS_TO_PASS, ast.literal_eval(limits)):
528 cur_limit = resource.getrlimit(rlim)
529 if cur_limit != limit:
530 # Turn the number into a symbolic name for logging.
531 name = "RLIMIT_???"
532 for name, num in resource.__dict__.items():
533 if name.startswith("RLIMIT_") and num == rlim:
534 break
535 logging.debug(
536 "Restoring user rlimit %s from %r to %r", name, cur_limit, limit
537 )
Mike Frysinger12d055b2022-06-23 12:26:47 -0400538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 resource.setrlimit(rlim, limit)
Mike Frysinger12d055b2022-06-23 12:26:47 -0400540
541
David James56e6c2c2012-10-24 23:54:41 -0700542def _SudoCommand():
Alex Klein1699fab2022-09-08 08:46:06 -0600543 """Get the 'sudo' command, along with all needed environment variables."""
David James56e6c2c2012-10-24 23:54:41 -0700544
Alex Klein1699fab2022-09-08 08:46:06 -0600545 # Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
546 # scripts in the chroot know what variables to pass through.
547 cmd = ["sudo"]
548 for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
549 value = os.environ.get(key)
550 if value is not None:
551 cmd += ["%s=%s" % (key, value)]
David James56e6c2c2012-10-24 23:54:41 -0700552
Alex Kleinef517832023-01-13 12:06:51 -0700553 # We keep PATH not for the chroot but for the re-exec & for programs we
554 # might run before we chroot into the SDK. The process that enters the SDK
555 # itself will take care of initializing PATH to the right value then. But
556 # we can't override the system's default PATH for root as that will hide
557 # /sbin.
Alex Klein1699fab2022-09-08 08:46:06 -0600558 cmd += ["CHROMEOS_SUDO_PATH=%s" % os.environ.get("PATH", "")]
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 # Pass along current rlimit settings so we can restore them.
561 cmd += [f"CHROMEOS_SUDO_RLIMITS={_GetRlimits()}"]
Mike Frysinger12d055b2022-06-23 12:26:47 -0400562
Alex Klein1699fab2022-09-08 08:46:06 -0600563 # Pass in the path to the depot_tools so that users can access them from
564 # within the chroot.
565 cmd += ["DEPOT_TOOLS=%s" % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500566
Alex Klein1699fab2022-09-08 08:46:06 -0600567 return cmd
David James56e6c2c2012-10-24 23:54:41 -0700568
569
Josh Triplett472a4182013-03-08 11:48:57 -0800570def _ReportMissing(missing):
Alex Klein1699fab2022-09-08 08:46:06 -0600571 """Report missing utilities, then exit.
Josh Triplett472a4182013-03-08 11:48:57 -0800572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700574 missing: List of missing utilities, as returned by
575 osutils.FindMissingBinaries. If non-empty, will not return.
Alex Klein1699fab2022-09-08 08:46:06 -0600576 """
Josh Triplett472a4182013-03-08 11:48:57 -0800577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 if missing:
579 raise SystemExit(
580 "The tool(s) %s were not found.\n"
581 "Please install the appropriate package in your host.\n"
582 "Example(ubuntu):\n"
583 " sudo apt-get install <packagename>" % ", ".join(missing)
584 )
Josh Triplett472a4182013-03-08 11:48:57 -0800585
586
587def _ProxySimSetup(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600588 """Set up proxy simulator, and return only in the child environment.
Josh Triplett472a4182013-03-08 11:48:57 -0800589
Alex Klein1699fab2022-09-08 08:46:06 -0600590 TODO: Ideally, this should support multiple concurrent invocations of
591 cros_sdk --proxy-sim; currently, such invocations will conflict with each
592 other due to the veth device names and IP addresses. Either this code would
593 need to generate fresh, unused names for all of these before forking, or it
594 would need to support multiple concurrent cros_sdk invocations sharing one
595 proxy and allowing it to exit when unused (without counting on any local
596 service-management infrastructure on the host).
597 """
Josh Triplett472a4182013-03-08 11:48:57 -0800598
Alex Klein1699fab2022-09-08 08:46:06 -0600599 may_need_mpm = False
600 apache_bin = osutils.Which("apache2")
Josh Triplett472a4182013-03-08 11:48:57 -0800601 if apache_bin is None:
Alex Klein1699fab2022-09-08 08:46:06 -0600602 apache_bin = osutils.Which("apache2", PROXY_APACHE_FALLBACK_PATH)
603 if apache_bin is None:
604 _ReportMissing(("apache2",))
605 else:
606 may_need_mpm = True
Josh Triplett472a4182013-03-08 11:48:57 -0800607
Alex Klein1699fab2022-09-08 08:46:06 -0600608 # Module names and .so names included for ease of grepping.
609 apache_modules = [
610 ("proxy_module", "mod_proxy.so"),
611 ("proxy_connect_module", "mod_proxy_connect.so"),
612 ("proxy_http_module", "mod_proxy_http.so"),
613 ("proxy_ftp_module", "mod_proxy_ftp.so"),
614 ]
Josh Triplett472a4182013-03-08 11:48:57 -0800615
Alex Kleinef517832023-01-13 12:06:51 -0700616 # Find the apache module directory and make sure it has the modules we need.
Alex Klein1699fab2022-09-08 08:46:06 -0600617 module_dirs = {}
618 for g in PROXY_APACHE_MODULE_GLOBS:
619 for _, so in apache_modules:
620 for f in glob.glob(os.path.join(g, so)):
621 module_dirs.setdefault(os.path.dirname(f), []).append(so)
622 for apache_module_path, modules_found in module_dirs.items():
623 if len(modules_found) == len(apache_modules):
624 break
625 else:
Alex Kleinef517832023-01-13 12:06:51 -0700626 # Appease cros lint, which doesn't understand that this else block will
627 # not fall through to the subsequent code which relies on
628 # apache_module_path.
Alex Klein1699fab2022-09-08 08:46:06 -0600629 apache_module_path = None
630 raise SystemExit(
Alex Kleinef517832023-01-13 12:06:51 -0700631 "Could not find apache module path containing all required "
632 "modules: %s" % ", ".join(so for mod, so in apache_modules)
Alex Klein1699fab2022-09-08 08:46:06 -0600633 )
Josh Triplett472a4182013-03-08 11:48:57 -0800634
Alex Klein1699fab2022-09-08 08:46:06 -0600635 def check_add_module(name):
636 so = "mod_%s.so" % name
637 if os.access(os.path.join(apache_module_path, so), os.F_OK):
638 mod = "%s_module" % name
639 apache_modules.append((mod, so))
640 return True
641 return False
Josh Triplett472a4182013-03-08 11:48:57 -0800642
Alex Klein1699fab2022-09-08 08:46:06 -0600643 check_add_module("authz_core")
644 if may_need_mpm:
645 for mpm in PROXY_APACHE_MPMS:
646 if check_add_module("mpm_%s" % mpm):
647 break
Josh Triplett472a4182013-03-08 11:48:57 -0800648
Alex Klein1699fab2022-09-08 08:46:06 -0600649 veth_host = "%s-host" % PROXY_VETH_PREFIX
650 veth_guest = "%s-guest" % PROXY_VETH_PREFIX
Josh Triplett472a4182013-03-08 11:48:57 -0800651
Alex Klein1699fab2022-09-08 08:46:06 -0600652 # Set up locks to sync the net namespace setup. We need the child to create
Alex Kleinef517832023-01-13 12:06:51 -0700653 # the net ns first, and then have the parent assign the guest end of the
654 # veth interface to the child's new network namespace & bring up the proxy.
655 # Only then can the child move forward and rely on the network being up.
Alex Klein1699fab2022-09-08 08:46:06 -0600656 ns_create_lock = locking.PipeLock()
657 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800658
Alex Klein1699fab2022-09-08 08:46:06 -0600659 pid = os.fork()
660 if not pid:
661 # Create our new isolated net namespace.
662 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500663
Alex Klein1699fab2022-09-08 08:46:06 -0600664 # Signal the parent the ns is ready to be configured.
665 ns_create_lock.Post()
666 del ns_create_lock
667
668 # Wait for the parent to finish setting up the ns/proxy.
669 ns_setup_lock.Wait()
670 del ns_setup_lock
671
672 # Set up child side of the network.
673 commands = (
674 ("ip", "link", "set", "up", "lo"),
675 (
676 "ip",
677 "address",
678 "add",
679 "%s/%u" % (PROXY_GUEST_IP, PROXY_NETMASK),
680 "dev",
681 veth_guest,
682 ),
683 ("ip", "link", "set", veth_guest, "up"),
684 )
685 try:
686 for cmd in commands:
687 cros_build_lib.dbg_run(cmd)
688 except cros_build_lib.RunCommandError as e:
689 cros_build_lib.Die("Proxy setup failed!\n%s", e)
690
691 proxy_url = "http://%s:%u" % (PROXY_HOST_IP, PROXY_PORT)
692 for proto in ("http", "https", "ftp"):
693 os.environ[proto + "_proxy"] = proxy_url
694 for v in ("all_proxy", "RSYNC_PROXY", "no_proxy"):
695 os.environ.pop(v, None)
696 return
697
698 # Set up parent side of the network.
699 uid = int(os.environ.get("SUDO_UID", "0"))
700 gid = int(os.environ.get("SUDO_GID", "0"))
701 if uid == 0 or gid == 0:
702 for username in PROXY_APACHE_FALLBACK_USERS:
703 try:
704 pwnam = pwd.getpwnam(username)
705 uid, gid = pwnam.pw_uid, pwnam.pw_gid
706 break
707 except KeyError:
708 continue
709 if uid == 0 or gid == 0:
710 raise SystemExit("Could not find a non-root user to run Apache as")
711
712 chroot_parent, chroot_base = os.path.split(options.chroot)
713 pid_file = os.path.join(chroot_parent, ".%s-apache-proxy.pid" % chroot_base)
714 log_file = os.path.join(chroot_parent, ".%s-apache-proxy.log" % chroot_base)
715
716 # Wait for the child to create the net ns.
717 ns_create_lock.Wait()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500718 del ns_create_lock
719
Alex Klein1699fab2022-09-08 08:46:06 -0600720 apache_directives = [
721 "User #%u" % uid,
722 "Group #%u" % gid,
723 "PidFile %s" % pid_file,
724 "ErrorLog %s" % log_file,
725 "Listen %s:%u" % (PROXY_HOST_IP, PROXY_PORT),
726 "ServerName %s" % PROXY_HOST_IP,
727 "ProxyRequests On",
728 "AllowCONNECT %s" % " ".join(str(x) for x in PROXY_CONNECT_PORTS),
729 ] + [
730 "LoadModule %s %s" % (mod, os.path.join(apache_module_path, so))
731 for (mod, so) in apache_modules
732 ]
733 commands = (
734 (
735 "ip",
736 "link",
737 "add",
738 "name",
739 veth_host,
740 "type",
741 "veth",
742 "peer",
743 "name",
744 veth_guest,
745 ),
746 (
747 "ip",
748 "address",
749 "add",
750 "%s/%u" % (PROXY_HOST_IP, PROXY_NETMASK),
751 "dev",
752 veth_host,
753 ),
754 ("ip", "link", "set", veth_host, "up"),
755 (
756 [apache_bin, "-f", "/dev/null"]
757 + [arg for d in apache_directives for arg in ("-C", d)]
758 ),
759 ("ip", "link", "set", veth_guest, "netns", str(pid)),
760 )
761 cmd = None # Make cros lint happy.
762 try:
763 for cmd in commands:
764 cros_build_lib.dbg_run(cmd)
765 except cros_build_lib.RunCommandError as e:
766 # Clean up existing interfaces, if any.
767 cmd_cleanup = ("ip", "link", "del", veth_host)
768 try:
769 cros_build_lib.run(cmd_cleanup, print_cmd=False)
770 except cros_build_lib.RunCommandError:
771 logging.error("running %r failed", cmd_cleanup)
772 cros_build_lib.Die("Proxy network setup failed!\n%s", e)
773
774 # Signal the child that the net ns/proxy is fully configured now.
775 ns_setup_lock.Post()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500776 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800777
Alex Klein1699fab2022-09-08 08:46:06 -0600778 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800779
780
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400781def _BuildReExecCommand(argv, opts) -> List[str]:
Alex Klein1699fab2022-09-08 08:46:06 -0600782 """Generate new command for self-reexec."""
783 # Make sure to preserve the active Python executable in case the version
784 # we're running as is not the default one found via the (new) $PATH.
785 cmd = _SudoCommand() + ["--"]
786 if opts.strace:
787 cmd += ["strace"] + shlex.split(opts.strace_arguments) + ["--"]
788 return cmd + [sys.executable] + argv
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400789
790
791def _ReExecuteIfNeeded(argv, opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600792 """Re-execute cros_sdk as root.
David James56e6c2c2012-10-24 23:54:41 -0700793
Alex Klein1699fab2022-09-08 08:46:06 -0600794 Also unshare the mount namespace so as to ensure that processes outside
795 the chroot can't mess with our mounts.
796 """
797 if osutils.IsNonRootUser():
798 cmd = _BuildReExecCommand(argv, opts)
799 logging.debug(
800 "Reexecing self via sudo:\n%s", cros_build_lib.CmdToStr(cmd)
801 )
802 os.execvp(cmd[0], cmd)
David James56e6c2c2012-10-24 23:54:41 -0700803
804
Mike Frysinger34db8692013-11-11 14:54:08 -0500805def _CreateParser(sdk_latest_version, bootstrap_latest_version):
Alex Klein1699fab2022-09-08 08:46:06 -0600806 """Generate and return the parser with all the options."""
807 usage = (
808 "usage: %(prog)s [options] "
809 "[VAR1=val1 ... VAR2=val2] [--] [command [args]]"
810 )
811 parser = commandline.ArgumentParser(
812 usage=usage, description=__doc__, caching=True
813 )
Brian Harring218e13c2012-10-10 16:21:26 -0700814
Alex Klein1699fab2022-09-08 08:46:06 -0600815 # Global options.
816 default_chroot = os.path.join(
817 constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR
818 )
819 parser.add_argument(
820 "--chroot",
821 dest="chroot",
822 default=default_chroot,
823 type="path",
824 help=("SDK chroot dir name [%s]" % constants.DEFAULT_CHROOT_DIR),
825 )
826 parser.add_argument(
827 "--nouse-image",
828 dest="use_image",
829 action="store_false",
830 default=False,
831 help="Do not mount the chroot on a loopback image; "
832 "instead, create it directly in a directory.",
833 )
834 parser.add_argument(
835 "--use-image",
836 dest="use_image",
837 action="store_true",
838 default=False,
839 help="Mount the chroot on a loopback image "
840 "instead of creating it directly in a directory.",
841 )
Brian Harringb938c782012-02-29 15:14:38 -0800842
Alex Klein1699fab2022-09-08 08:46:06 -0600843 parser.add_argument(
844 "--chrome-root",
845 "--chrome_root",
846 type="path",
847 help="Mount this chrome root into the SDK chroot",
848 )
849 parser.add_argument(
850 "--chrome_root_mount",
851 type="path",
852 help="Mount chrome into this path inside SDK chroot",
853 )
854 parser.add_argument(
855 "--nousepkg",
856 action="store_true",
857 default=False,
858 help="Do not use binary packages when creating a chroot.",
859 )
860 parser.add_argument(
861 "-u",
862 "--url",
863 dest="sdk_url",
864 help="Use sdk tarball located at this url. Use file:// "
865 "for local files.",
866 )
867 parser.add_argument(
868 "--sdk-version",
869 help=(
870 "Use this sdk version. For prebuilt, current is %r"
871 ", for bootstrapping it is %r."
872 % (sdk_latest_version, bootstrap_latest_version)
873 ),
874 )
875 parser.add_argument(
876 "--goma_dir",
877 type="path",
878 help="Goma installed directory to mount into the chroot.",
879 )
880 parser.add_argument(
881 "--goma_client_json",
882 type="path",
883 help="Service account json file to use goma on bot. "
884 "Mounted into the chroot.",
885 )
886 parser.add_argument(
887 "--reclient-dir",
888 type="path",
889 help="Reclient installed directory to mount into the chroot.",
890 )
891 parser.add_argument(
892 "--reproxy-cfg-file",
893 type="path",
894 help="Config file for re-client's reproxy used for remoteexec.",
895 )
896 parser.add_argument(
897 "--skip-chroot-upgrade",
898 dest="chroot_upgrade",
899 action="store_false",
900 default=True,
Alex Kleinef517832023-01-13 12:06:51 -0700901 help="Skip automatic SDK and toolchain upgrade when entering the "
902 "chroot. Never guaranteed to work, especially as ToT moves forward.",
Alex Klein1699fab2022-09-08 08:46:06 -0600903 )
Yong Hong84ba9172018-02-07 01:37:42 +0800904
Alex Klein1699fab2022-09-08 08:46:06 -0600905 # Use type=str instead of type='path' to prevent the given path from being
Alex Kleinef517832023-01-13 12:06:51 -0700906 # transferred to absolute path automatically.
Alex Klein1699fab2022-09-08 08:46:06 -0600907 parser.add_argument(
908 "--working-dir",
909 type=str,
910 help="Run the command in specific working directory in "
911 "chroot. If the given directory is a relative "
912 "path, this program will transfer the path to "
913 "the corresponding one inside chroot.",
914 )
Yong Hong84ba9172018-02-07 01:37:42 +0800915
Alex Klein1699fab2022-09-08 08:46:06 -0600916 parser.add_argument("commands", nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500917
Alex Klein1699fab2022-09-08 08:46:06 -0600918 # Commands.
919 group = parser.add_argument_group("Commands")
Mike Frysinger79024a32021-04-05 03:28:35 -0400920 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600921 "--enter",
922 action="store_true",
923 default=False,
924 help="Enter the SDK chroot. Implies --create.",
925 )
Mike Frysinger79024a32021-04-05 03:28:35 -0400926 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600927 "--create",
928 action="store_true",
929 default=False,
Alex Klein22690a12022-11-17 10:56:09 -0700930 help="Create the chroot only if it does not already exist. Downloads "
931 "the SDK only if needed, even if --download explicitly passed.",
Alex Klein1699fab2022-09-08 08:46:06 -0600932 )
933 group.add_argument(
934 "--bootstrap",
935 action="store_true",
936 default=False,
937 help="Build everything from scratch, including the sdk. "
938 "Use this only if you need to validate a change "
939 "that affects SDK creation itself (toolchain and "
940 "build are typically the only folk who need this). "
941 "Note this will quite heavily slow down the build. "
942 "This option implies --create --nousepkg.",
943 )
944 group.add_argument(
945 "-r",
946 "--replace",
947 action="store_true",
948 default=False,
949 help="Replace an existing SDK chroot. Basically an alias "
950 "for --delete --create.",
951 )
952 group.add_argument(
953 "--delete",
954 action="store_true",
955 default=False,
956 help="Delete the current SDK chroot if it exists.",
957 )
958 group.add_argument(
959 "--force",
960 action="store_true",
961 default=False,
962 help="Force unmount/delete of the current SDK chroot even if "
963 "obtaining the write lock fails.",
964 )
965 group.add_argument(
966 "--unmount",
967 action="store_true",
968 default=False,
969 help="Unmount and clean up devices associated with the "
970 "SDK chroot if it exists. This does not delete the "
971 "backing image file, so the same chroot can be later "
972 "re-mounted for reuse. To fully delete the chroot, use "
973 "--delete. This is primarily useful for working on "
974 "cros_sdk or the chroot setup; you should not need it "
975 "under normal circumstances.",
976 )
977 group.add_argument(
978 "--download",
979 action="store_true",
980 default=False,
981 help="Download the sdk.",
982 )
983 group.add_argument(
984 "--snapshot-create",
985 metavar="SNAPSHOT_NAME",
986 help="Create a snapshot of the chroot. Requires that the chroot was "
987 "created without the --nouse-image option.",
988 )
989 group.add_argument(
990 "--snapshot-restore",
991 metavar="SNAPSHOT_NAME",
992 help="Restore the chroot to a previously created snapshot.",
993 )
994 group.add_argument(
995 "--snapshot-delete",
996 metavar="SNAPSHOT_NAME",
997 help="Delete a previously created snapshot. Deleting a snapshot that "
998 "does not exist is not an error.",
999 )
1000 group.add_argument(
1001 "--snapshot-list",
1002 action="store_true",
1003 default=False,
1004 help="List existing snapshots of the chroot and exit.",
1005 )
1006 commands = group
Mike Frysinger80dfce92014-04-21 10:58:53 -04001007
Alex Klein1699fab2022-09-08 08:46:06 -06001008 # Namespace options.
1009 group = parser.add_argument_group("Namespaces")
1010 group.add_argument(
1011 "--proxy-sim",
1012 action="store_true",
1013 default=False,
1014 help="Simulate a restrictive network requiring an outbound" " proxy.",
1015 )
1016 for ns, default in (("pid", True), ("net", None)):
1017 group.add_argument(
1018 f"--ns-{ns}",
1019 default=default,
1020 action="store_true",
1021 help=f"Create a new {ns} namespace.",
1022 )
1023 group.add_argument(
1024 f"--no-ns-{ns}",
1025 dest=f"ns_{ns}",
1026 action="store_false",
1027 help=f"Do not create a new {ns} namespace.",
1028 )
Mike Frysinger5f5c70b2022-07-19 12:55:21 -04001029
Alex Klein1699fab2022-09-08 08:46:06 -06001030 # Debug options.
1031 group = parser.debug_group
1032 group.add_argument(
1033 "--strace",
1034 action="store_true",
1035 help="Run cros_sdk through strace after re-exec via sudo",
1036 )
1037 group.add_argument(
1038 "--strace-arguments",
1039 default="",
1040 help="Extra strace options (shell quoting permitted)",
1041 )
Mike Frysinger34db8692013-11-11 14:54:08 -05001042
Alex Klein1699fab2022-09-08 08:46:06 -06001043 # Internal options.
1044 group = parser.add_argument_group(
1045 "Internal Chromium OS Build Team Options",
1046 "Caution: these are for meant for the Chromium OS build team only",
1047 )
1048 group.add_argument(
1049 "--buildbot-log-version",
1050 default=False,
1051 action="store_true",
1052 help="Log SDK version for buildbot consumption",
1053 )
1054
1055 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -05001056
1057
1058def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001059 # Turn on strict sudo checks.
1060 cros_build_lib.STRICT_SUDO = True
1061 conf = key_value_store.LoadFile(
1062 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
1063 ignore_missing=True,
1064 )
1065 sdk_latest_version = conf.get("SDK_LATEST_VERSION", "<unknown>")
1066 bootstrap_frozen_version = conf.get("BOOTSTRAP_FROZEN_VERSION", "<unknown>")
Manoj Gupta55a63092019-06-13 11:47:13 -07001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
1069 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
1070 bootstrap_latest_version = (
1071 sdk_latest_version
1072 if bootstrap_frozen_version == "<unknown>"
1073 else bootstrap_frozen_version
1074 )
1075 parser, commands = _CreateParser(
1076 sdk_latest_version, bootstrap_latest_version
1077 )
1078 options = parser.parse_args(argv)
1079 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -08001080
Alex Klein1699fab2022-09-08 08:46:06 -06001081 # Some basic checks first, before we ask for sudo credentials.
1082 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -08001083
Alex Klein1699fab2022-09-08 08:46:06 -06001084 host = os.uname()[4]
1085 if host != "x86_64":
1086 cros_build_lib.Die(
1087 "cros_sdk is currently only supported on x86_64; you're running"
1088 " %s. Please find a x86_64 machine." % (host,)
1089 )
Brian Harring1790ac42012-09-23 08:53:33 -07001090
Alex Klein1699fab2022-09-08 08:46:06 -06001091 # Merge the outside PATH setting if we re-execed ourselves.
1092 if "CHROMEOS_SUDO_PATH" in os.environ:
1093 os.environ["PATH"] = "%s:%s" % (
1094 os.environ.pop("CHROMEOS_SUDO_PATH"),
1095 os.environ["PATH"],
1096 )
Mike Frysinger2bda4d12020-07-14 11:15:49 -04001097
Alex Klein1699fab2022-09-08 08:46:06 -06001098 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
1099 if options.proxy_sim:
1100 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
1101 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -08001102
Alex Klein1699fab2022-09-08 08:46:06 -06001103 if (
1104 sdk_latest_version == "<unknown>"
1105 or bootstrap_latest_version == "<unknown>"
1106 ):
1107 cros_build_lib.Die(
1108 "No SDK version was found. "
1109 "Are you in a Chromium source tree instead of Chromium OS?\n\n"
1110 "Please change to a directory inside your Chromium OS source tree\n"
1111 "and retry. If you need to setup a Chromium OS source tree, see\n"
1112 " https://dev.chromium.org/chromium-os/developer-guide"
1113 )
Benjamin Gordon040a1162017-06-29 13:44:47 -06001114
Alex Klein1699fab2022-09-08 08:46:06 -06001115 any_snapshot_operation = (
1116 options.snapshot_create
1117 or options.snapshot_restore
1118 or options.snapshot_delete
1119 or options.snapshot_list
1120 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001121
Alex Klein1699fab2022-09-08 08:46:06 -06001122 if (
1123 options.snapshot_delete
1124 and options.snapshot_delete == options.snapshot_restore
1125 ):
1126 parser.error(
1127 "Cannot --snapshot_delete the same snapshot you are "
1128 "restoring with --snapshot_restore."
1129 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001130
Alex Klein1699fab2022-09-08 08:46:06 -06001131 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -08001132
Alex Klein1699fab2022-09-08 08:46:06 -06001133 lock_path = os.path.dirname(options.chroot)
1134 lock_path = os.path.join(
1135 lock_path, ".%s_lock" % os.path.basename(options.chroot).lstrip(".")
1136 )
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 # Expand out the aliases...
1139 if options.replace:
1140 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001141
Alex Klein1699fab2022-09-08 08:46:06 -06001142 if options.bootstrap:
1143 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -08001144
Alex Klein1699fab2022-09-08 08:46:06 -06001145 # If a command is not given, default to enter.
1146 # pylint: disable=protected-access
1147 # This _group_actions access sucks, but upstream decided to not include an
1148 # alternative to optparse's option_list, and this is what they recommend.
1149 options.enter |= not any(
1150 getattr(options, x.dest) for x in commands._group_actions
1151 )
1152 # pylint: enable=protected-access
1153 options.enter |= bool(chroot_command)
Brian Harring218e13c2012-10-10 16:21:26 -07001154
Alex Klein1699fab2022-09-08 08:46:06 -06001155 if (
1156 options.delete
1157 and not options.create
1158 and (options.enter or any_snapshot_operation)
1159 ):
1160 parser.error(
1161 "Trying to enter or snapshot the chroot when --delete "
1162 "was specified makes no sense."
1163 )
Brian Harring218e13c2012-10-10 16:21:26 -07001164
Alex Klein1699fab2022-09-08 08:46:06 -06001165 if options.unmount and (
1166 options.create or options.enter or any_snapshot_operation
1167 ):
1168 parser.error("--unmount cannot be specified with other chroot actions.")
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001169
Alex Klein1699fab2022-09-08 08:46:06 -06001170 if options.working_dir is not None and not os.path.isabs(
1171 options.working_dir
1172 ):
1173 options.working_dir = path_util.ToChrootPath(options.working_dir)
Yong Hong84ba9172018-02-07 01:37:42 +08001174
Alex Klein1699fab2022-09-08 08:46:06 -06001175 # If there is an existing chroot image and we're not removing it then force
1176 # use_image on. This ensures that people don't have to remember to pass
Alex Kleinef517832023-01-13 12:06:51 -07001177 # --use-image after a reboot to avoid losing access to their existing
1178 # chroot.
Alex Klein1699fab2022-09-08 08:46:06 -06001179 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1180 img_path = _ImageFileForChroot(options.chroot)
1181 if (
1182 not options.use_image
1183 and not options.delete
1184 and not options.unmount
1185 and os.path.exists(img_path)
1186 ):
1187 if chroot_exists:
1188 # If the chroot is already populated, make sure it has something
1189 # mounted on it before we assume it came from an image.
1190 cmd = ["mountpoint", "-q", options.chroot]
1191 if cros_build_lib.dbg_run(cmd, check=False).returncode == 0:
1192 options.use_image = True
Benjamin Gordon832a4412020-12-08 10:39:16 -07001193
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001194 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001195 logging.notice(
1196 "Existing chroot image %s found. Forcing --use-image on.",
1197 img_path,
1198 )
1199 options.use_image = True
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001200
Alex Klein1699fab2022-09-08 08:46:06 -06001201 if any_snapshot_operation and not options.use_image:
1202 if os.path.exists(img_path):
1203 options.use_image = True
1204 else:
1205 cros_build_lib.Die(
1206 "Snapshot operations are not compatible with " "--nouse-image."
1207 )
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001208
Alex Klein1699fab2022-09-08 08:46:06 -06001209 # Discern if we need to create the chroot.
1210 if (
1211 options.use_image
1212 and not chroot_exists
1213 and not options.delete
1214 and not options.unmount
1215 and not missing_image_tools
1216 and os.path.exists(img_path)
1217 ):
1218 # Try to re-mount an existing image in case the user has rebooted.
1219 with locking.FileLock(lock_path, "chroot lock") as lock:
1220 logging.debug("Checking if existing chroot image can be mounted.")
1221 lock.write_lock()
1222 cros_sdk_lib.MountChroot(options.chroot, create=False)
1223 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1224 if chroot_exists:
1225 logging.notice("Mounted existing image %s on chroot", img_path)
1226
1227 # Finally, flip create if necessary.
1228 if options.enter or options.snapshot_create:
1229 options.create |= not chroot_exists
1230
1231 # Make sure we will download if we plan to create.
1232 options.download |= options.create
1233
Alex Kleinef517832023-01-13 12:06:51 -07001234 # Anything that needs to manipulate the main chroot mount or communicate
1235 # with LVM needs to be done here before we enter the new namespaces.
Alex Klein1699fab2022-09-08 08:46:06 -06001236
1237 # If deleting, do it regardless of the use_image flag so that a
1238 # previously-created loopback chroot can also be cleaned up.
1239 if options.delete:
1240 # Set a timeout of 300 seconds when getting the lock.
1241 with locking.FileLock(
1242 lock_path, "chroot lock", blocking_timeout=300
1243 ) as lock:
1244 try:
1245 lock.write_lock()
1246 except timeout_util.TimeoutError as e:
1247 logging.error(
1248 "Acquiring write_lock on %s failed: %s", lock_path, e
1249 )
1250 if not options.force:
1251 cros_build_lib.Die(
1252 "Exiting; use --force to continue w/o lock."
1253 )
1254 else:
1255 logging.warning(
1256 "cros_sdk was invoked with force option, continuing."
1257 )
1258 logging.notice("Deleting chroot.")
1259 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
1260
Alex Kleinef517832023-01-13 12:06:51 -07001261 # If cleanup was requested, we have to do it while we're still in the
1262 # original namespace. Since cleaning up the mount will interfere with any
1263 # other commands, we exit here. The check above should have made sure that
1264 # no other action was requested, anyway.
Alex Klein1699fab2022-09-08 08:46:06 -06001265 if options.unmount:
1266 # Set a timeout of 300 seconds when getting the lock.
1267 with locking.FileLock(
1268 lock_path, "chroot lock", blocking_timeout=300
1269 ) as lock:
1270 try:
1271 lock.write_lock()
1272 except timeout_util.TimeoutError as e:
1273 logging.error(
1274 "Acquiring write_lock on %s failed: %s", lock_path, e
1275 )
1276 logging.warning(
Alex Kleinef517832023-01-13 12:06:51 -07001277 "Continuing with CleanupChroot(%s), which will umount the "
1278 "tree.",
Alex Klein1699fab2022-09-08 08:46:06 -06001279 options.chroot,
1280 )
Alex Kleinef517832023-01-13 12:06:51 -07001281 # We can call CleanupChroot (which calls
1282 # cros_sdk_lib.CleanupChrootMount) even if we don't get the lock
1283 # because it will attempt to unmount the tree and will print
1284 # diagnostic information from 'fuser', 'lsof', and 'ps'.
Alex Klein1699fab2022-09-08 08:46:06 -06001285 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=False)
1286 sys.exit(0)
1287
1288 # Make sure the main chroot mount is visible. Contents will be filled in
1289 # below if needed.
1290 if options.create and options.use_image:
1291 if missing_image_tools:
1292 raise SystemExit(
1293 """The tool(s) %s were not found.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001294Please make sure the lvm2 and thin-provisioning-tools packages
1295are installed on your host.
1296Example(ubuntu):
1297 sudo apt-get install lvm2 thin-provisioning-tools
1298
1299If you want to run without lvm2, pass --nouse-image (chroot
Alex Klein1699fab2022-09-08 08:46:06 -06001300snapshots will be unavailable)."""
1301 % ", ".join(missing_image_tools)
1302 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001303
Alex Klein1699fab2022-09-08 08:46:06 -06001304 logging.debug("Making sure chroot image is mounted.")
1305 with locking.FileLock(lock_path, "chroot lock") as lock:
1306 lock.write_lock()
1307 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
1308 cros_build_lib.Die(
1309 "Unable to mount %s on chroot",
1310 _ImageFileForChroot(options.chroot),
1311 )
1312 logging.notice(
1313 "Mounted %s on chroot", _ImageFileForChroot(options.chroot)
1314 )
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001315
Alex Klein1699fab2022-09-08 08:46:06 -06001316 # Snapshot operations will always need the VG/LV, but other actions won't.
1317 if any_snapshot_operation:
1318 with locking.FileLock(lock_path, "chroot lock") as lock:
1319 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
1320 options.chroot
1321 )
1322 if not chroot_vg or not chroot_lv:
1323 cros_build_lib.Die(
1324 "Unable to find VG/LV for chroot %s", options.chroot
1325 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001326
Alex Kleinef517832023-01-13 12:06:51 -07001327 # Delete snapshot before creating a new one. This allows the user to
1328 # throw out old state, create a new snapshot, and enter the chroot
1329 # in a single call to cros_sdk. Since restore involves deleting,
1330 # also do it before creating.
Alex Klein1699fab2022-09-08 08:46:06 -06001331 if options.snapshot_restore:
1332 lock.write_lock()
1333 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1334 if options.snapshot_restore not in valid_snapshots:
1335 cros_build_lib.Die(
Alex Kleinef517832023-01-13 12:06:51 -07001336 "%s is not a valid snapshot to restore to. "
1337 "Valid snapshots: %s",
Alex Klein1699fab2022-09-08 08:46:06 -06001338 options.snapshot_restore,
1339 ", ".join(valid_snapshots),
1340 )
1341 osutils.UmountTree(options.chroot)
1342 if not RestoreChrootSnapshot(
1343 options.snapshot_restore, chroot_vg, chroot_lv
1344 ):
1345 cros_build_lib.Die("Unable to restore chroot to snapshot.")
1346 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
1347 cros_build_lib.Die(
1348 "Unable to mount restored snapshot onto chroot."
1349 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001350
Alex Kleinef517832023-01-13 12:06:51 -07001351 # Use a read lock for snapshot delete and create even though they
1352 # modify the filesystem, because they don't modify the mounted
1353 # chroot itself. The underlying LVM commands take their own locks,
1354 # so conflicting concurrent operations here may crash cros_sdk, but
1355 # won't corrupt the chroot image. This tradeoff seems worth it to
1356 # allow snapshot operations on chroots that have a process inside.
Alex Klein1699fab2022-09-08 08:46:06 -06001357 if options.snapshot_delete:
1358 lock.read_lock()
1359 DeleteChrootSnapshot(
1360 options.snapshot_delete, chroot_vg, chroot_lv
1361 )
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001362
Alex Klein1699fab2022-09-08 08:46:06 -06001363 if options.snapshot_create:
1364 lock.read_lock()
1365 if not CreateChrootSnapshot(
1366 options.snapshot_create, chroot_vg, chroot_lv
1367 ):
1368 cros_build_lib.Die("Unable to create snapshot.")
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001369
Alex Klein1699fab2022-09-08 08:46:06 -06001370 img_path = _ImageFileForChroot(options.chroot)
1371 if (
1372 options.use_image
1373 and os.path.exists(options.chroot)
1374 and os.path.exists(img_path)
1375 ):
1376 img_stat = os.stat(img_path)
1377 img_used_bytes = img_stat.st_blocks * 512
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001378
Alex Klein1699fab2022-09-08 08:46:06 -06001379 mount_stat = os.statvfs(options.chroot)
1380 mount_used_bytes = mount_stat.f_frsize * (
1381 mount_stat.f_blocks - mount_stat.f_bfree
1382 )
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001383
Alex Klein1699fab2022-09-08 08:46:06 -06001384 extra_gbs = (img_used_bytes - mount_used_bytes) // 2**30
1385 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1386 logging.notice(
1387 "%s is using %s GiB more than needed. Running "
1388 "fstrim in background.",
1389 img_path,
1390 extra_gbs,
1391 )
1392 pid = os.fork()
1393 if pid == 0:
1394 try:
1395 # Directly call Popen to run fstrim concurrently.
1396 cmd = ["fstrim", options.chroot]
1397 subprocess.Popen(cmd, close_fds=True, shell=False)
1398 except subprocess.SubprocessError as e:
1399 logging.warning(
1400 "Running fstrim failed. Consider running fstrim on "
1401 "your chroot manually.\n%s",
1402 e,
1403 )
1404 os._exit(0) # pylint: disable=protected-access
1405 os.waitpid(pid, 0)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001406
Alex Kleinef517832023-01-13 12:06:51 -07001407 # Enter a new set of namespaces. Everything after here cannot directly
1408 # affect the hosts's mounts or alter LVM volumes.
Alex Klein1699fab2022-09-08 08:46:06 -06001409 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001410
Alex Klein1699fab2022-09-08 08:46:06 -06001411 if options.snapshot_list:
1412 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1413 print(snap)
1414 sys.exit(0)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001415
Alex Klein1699fab2022-09-08 08:46:06 -06001416 if not options.sdk_version:
1417 sdk_version = (
1418 bootstrap_latest_version
1419 if options.bootstrap
1420 else sdk_latest_version
1421 )
Yong Hong4e29b622018-02-05 14:31:10 +08001422 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001423 sdk_version = options.sdk_version
1424 if options.buildbot_log_version:
1425 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001426
Alex Klein1699fab2022-09-08 08:46:06 -06001427 # Based on selections, determine the tarball to fetch.
Alex Klein22690a12022-11-17 10:56:09 -07001428 urls = []
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001429 if options.download:
Alex Klein1699fab2022-09-08 08:46:06 -06001430 if options.sdk_url:
1431 urls = [options.sdk_url]
1432 else:
1433 urls = GetArchStageTarballs(sdk_version)
Brian Harring218e13c2012-10-10 16:21:26 -07001434
Alex Klein1699fab2022-09-08 08:46:06 -06001435 with locking.FileLock(lock_path, "chroot lock") as lock:
1436 if options.proxy_sim:
1437 _ProxySimSetup(options)
Brian Harring1790ac42012-09-23 08:53:33 -07001438
Alex Klein1699fab2022-09-08 08:46:06 -06001439 sdk_cache = os.path.join(options.cache_dir, "sdks")
1440 distfiles_cache = os.path.join(options.cache_dir, "distfiles")
1441 osutils.SafeMakedirsNonRoot(options.cache_dir)
1442
1443 for target in (sdk_cache, distfiles_cache):
1444 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
1445 if not os.path.exists(src):
1446 osutils.SafeMakedirsNonRoot(target)
1447 continue
1448 lock.write_lock(
1449 "Upgrade to %r needed but chroot is locked; please exit "
1450 "all instances so this upgrade can finish." % src
1451 )
1452 if not os.path.exists(src):
Alex Kleinef517832023-01-13 12:06:51 -07001453 # Note that while waiting for the write lock, src may've
1454 # vanished; it's a rare race during the upgrade process that's a
1455 # byproduct of us avoiding taking a write lock to do the src
1456 # check. If we took a write lock for that check, it would
1457 # effectively limit all cros_sdk for a chroot to a single
1458 # instance.
Alex Klein1699fab2022-09-08 08:46:06 -06001459 osutils.SafeMakedirsNonRoot(target)
1460 elif not os.path.exists(target):
1461 # Upgrade occurred, but a reversion, or something whacky
1462 # occurred writing to the old location. Wipe and continue.
1463 os.rename(src, target)
1464 else:
1465 # Upgrade occurred once already, but either a reversion or
1466 # some before/after separate cros_sdk usage is at play.
1467 # Wipe and continue.
1468 osutils.RmDir(src)
1469
Alex Klein1699fab2022-09-08 08:46:06 -06001470 mounted = False
1471 if options.create:
1472 lock.write_lock()
Alex Kleinef517832023-01-13 12:06:51 -07001473 # Recheck if the chroot is set up here before creating to make sure
1474 # we account for whatever the various delete/unmount/remount steps
1475 # above have done.
Alex Klein1699fab2022-09-08 08:46:06 -06001476 if cros_sdk_lib.IsChrootReady(options.chroot):
1477 logging.debug("Chroot already exists. Skipping creation.")
1478 else:
Alex Klein22690a12022-11-17 10:56:09 -07001479 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -06001480 cros_sdk_lib.CreateChroot(
1481 Path(options.chroot),
1482 Path(sdk_tarball),
1483 Path(options.cache_dir),
1484 usepkg=not options.bootstrap and not options.nousepkg,
1485 chroot_upgrade=options.chroot_upgrade,
1486 )
1487 mounted = True
Alex Klein22690a12022-11-17 10:56:09 -07001488 elif options.download:
1489 # Allow downloading only.
1490 lock.write_lock()
1491 FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -06001492
1493 if options.enter:
1494 lock.read_lock()
1495 if not mounted:
1496 cros_sdk_lib.MountChrootPaths(options.chroot)
1497 ret = EnterChroot(
1498 options.chroot,
1499 options.cache_dir,
1500 options.chrome_root,
1501 options.chrome_root_mount,
1502 options.goma_dir,
1503 options.goma_client_json,
1504 options.reclient_dir,
1505 options.reproxy_cfg_file,
1506 options.working_dir,
1507 chroot_command,
1508 )
1509 sys.exit(ret.returncode)