blob: 16e1081ae50d21b52f4596056935f499c105e116 [file] [log] [blame]
Mike Frysinger2de7f042012-07-10 04:45:03 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Manoj Gupta7fad04d2019-06-14 20:12:25 -07004
Mike Frysinger2f95cfc2015-06-04 04:00:26 -04005"""Manage SDK chroots.
6
7This script is used for manipulating local chroot environments; creating,
8deleting, downloading, etc. If given --enter (or no args), it defaults
9to an interactive bash shell within the chroot.
10
11If given args those are passed to the chroot environment, and executed.
12"""
Brian Harringb938c782012-02-29 15:14:38 -080013
Mike Frysinger2f95cfc2015-06-04 04:00:26 -040014import argparse
Mike Frysinger12d055b2022-06-23 12:26:47 -040015import ast
Josh Triplett472a4182013-03-08 11:48:57 -080016import glob
Chris McDonaldb55b7032021-06-17 16:41:32 -060017import logging
Brian Harringb938c782012-02-29 15:14:38 -080018import os
Mike Frysinger23b5cf52021-06-16 23:18:00 -040019from pathlib import Path
Josh Triplett472a4182013-03-08 11:48:57 -080020import pwd
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060021import random
Brian Norrisd37e2f72016-08-22 16:09:24 -070022import re
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -070023import resource
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040024import shlex
Sergey Frolov1cb46ec2020-12-09 21:46:16 -070025import subprocess
David James56e6c2c2012-10-24 23:54:41 -070026import sys
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040027from typing import List
Mike Frysingere852b072021-05-21 12:39:03 -040028import urllib.parse
Brian Harringb938c782012-02-29 15:14:38 -080029
Chris McDonaldb55b7032021-06-17 16:41:32 -060030from chromite.cbuildbot import cbuildbot_alerts
Brian Harringb6cf9142012-09-01 20:43:17 -070031from chromite.lib import commandline
Chris McDonaldb55b7032021-06-17 16:41:32 -060032from chromite.lib import constants
Brian Harringb938c782012-02-29 15:14:38 -080033from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060034from chromite.lib import cros_sdk_lib
Brian Harringb938c782012-02-29 15:14:38 -080035from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080036from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070037from chromite.lib import osutils
Yong Hong84ba9172018-02-07 01:37:42 +080038from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040039from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080040from chromite.lib import retry_util
Michael Mortensenbf296fb2020-06-18 18:21:54 -060041from chromite.lib import timeout_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050042from chromite.lib import toolchain
Mike Frysingere652ba12019-09-08 00:57:43 -040043from chromite.utils import key_value_store
44
Brian Harringb938c782012-02-29 15:14:38 -080045
Mike Frysingerf744d032022-05-07 20:38:39 -040046# Which compression algos the SDK tarball uses. We've used xz since 2012.
47COMPRESSION_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 = [
51 os.path.join(constants.SOURCE_ROOT, 'src/scripts/sdk_lib/enter_chroot.sh')
52]
Brian Harringb938c782012-02-29 15:14:38 -080053
Josh Triplett472a4182013-03-08 11:48:57 -080054# Proxy simulator configuration.
55PROXY_HOST_IP = '192.168.240.1'
56PROXY_PORT = 8080
57PROXY_GUEST_IP = '192.168.240.2'
58PROXY_NETMASK = 30
59PROXY_VETH_PREFIX = 'veth'
60PROXY_CONNECT_PORTS = (80, 443, 9418)
61PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
62PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
63PROXY_APACHE_FALLBACK_PATH = ':'.join(
Manoj Guptab12f7302019-06-03 16:40:14 -070064 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS)
Josh Triplett472a4182013-03-08 11:48:57 -080065PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
66
Josh Triplett9a495f62013-03-15 18:06:55 -070067# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080068NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080069
Josh Triplett472a4182013-03-08 11:48:57 -080070# Tools needed for --proxy-sim only.
71PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080072
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060073# Tools needed when use_image is true (the default).
74IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060075 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060076
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070077# As space is used inside the chroot, the empty space in chroot.img is
78# allocated. Deleting files inside the chroot doesn't automatically return the
79# used space to the OS. Over time, this tends to make the sparse chroot.img
80# less sparse even if the chroot contents don't currently need much space. We
81# can recover most of this unused space with fstrim, but that takes too much
82# time to run it every time. Instead, check the used space against the image
83# size after mounting the chroot and only call fstrim if it looks like we could
84# recover at least this many GiB.
85MAX_UNUSED_IMAGE_GBS = 20
86
Mike Frysingercc838832014-05-24 13:10:30 -040087
Brian Harring1790ac42012-09-23 08:53:33 -070088def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080089 """Returns the URL for a given arch/version"""
Mike Frysingerf744d032022-05-07 20:38:39 -040090 extension = {'xz': 'tar.xz'}
Manoj Guptab12f7302019-06-03 16:40:14 -070091 return [
92 toolchain.GetSdkURL(
93 suburl='cros-sdk-%s.%s' % (version, extension[compressor]))
94 for compressor in COMPRESSION_PREFERENCE
95 ]
Brian Harring1790ac42012-09-23 08:53:33 -070096
97
Mike Frysingerf744d032022-05-07 20:38:39 -040098def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050099 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200100
101 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500102 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200103 urls: List of URLs to try to download. Download will stop on first success.
104
105 Returns:
Mike Frysingerdaf57b82019-11-23 17:26:51 -0500106 Full path to the downloaded file.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700107
108 Raises:
Mike Frysingerdaf57b82019-11-23 17:26:51 -0500109 ValueError: None of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200110 """
Brian Harring1790ac42012-09-23 08:53:33 -0700111 # Note we track content length ourselves since certain versions of curl
112 # fail if asked to resume a complete file.
Brian Harring1790ac42012-09-23 08:53:33 -0700113 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Mike Frysingerdaf57b82019-11-23 17:26:51 -0500114 status_re = re.compile(br'^HTTP/[0-9]+(\.[0-9]+)? 200')
Mike Frysinger27e21b72018-07-12 14:20:21 -0400115 # pylint: disable=undefined-loop-variable
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200116 for url in urls:
Mike Frysingerf744d032022-05-07 20:38:39 -0400117 logging.notice('Downloading tarball %s ...', urls[0].rsplit('/', 1)[-1])
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400118 parsed = urllib.parse.urlparse(url)
Brian Harring1790ac42012-09-23 08:53:33 -0700119 tarball_name = os.path.basename(parsed.path)
120 if parsed.scheme in ('', 'file'):
121 if os.path.exists(parsed.path):
122 return parsed.path
123 continue
124 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700125 logging.debug('Attempting download from %s', url)
Manoj Guptab12f7302019-06-03 16:40:14 -0700126 result = retry_util.RunCurl(['-I', url],
127 print_cmd=False,
128 debug_level=logging.NOTICE,
129 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700130 successful = False
Mike Frysinger876a8e52022-06-23 18:07:30 -0400131 for header in result.stdout.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700132 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700133 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700134 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700135 successful = True
Mike Frysingerdaf57b82019-11-23 17:26:51 -0500136 elif header.lower().startswith(b'content-length:'):
137 content_length = int(header.split(b':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700138 if successful:
139 break
140 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200141 break
142 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700143 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200144
Brian Harringae0a5322012-09-15 01:46:51 -0700145 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700146 current_size = 0
147 if os.path.exists(tarball_dest):
148 current_size = os.path.getsize(tarball_dest)
149 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700150 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700151 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100152
Brian Harring1790ac42012-09-23 08:53:33 -0700153 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800154 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900155 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Manoj Guptab12f7302019-06-03 16:40:14 -0700156 print_cmd=False,
157 debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800158
Brian Harring1790ac42012-09-23 08:53:33 -0700159 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700160 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
161 # because we might have partial overlap between known prefixes.
Mike Frysingerf744d032022-05-07 20:38:39 -0400162 for p in Path(storage_dir).glob('cros-sdk-*'):
163 if p.name == tarball_name:
Brian Harring1790ac42012-09-23 08:53:33 -0700164 continue
Mike Frysingerf744d032022-05-07 20:38:39 -0400165 logging.info('Cleaning up old tarball: %s', p)
166 osutils.SafeUnlink(p)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200167
Brian Harringb938c782012-02-29 15:14:38 -0800168 return tarball_dest
169
170
Brian Harringae0a5322012-09-15 01:46:51 -0700171def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Joanna Wang1ec0c812021-11-17 17:41:27 -0800172 goma_dir, goma_client_json, reclient_dir, reproxy_cfg_file,
173 working_dir, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800174 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400175 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
Alex Klein875b30e2021-01-05 14:56:33 -0700176 if st.f_flag & os.ST_NOSUID:
Mike Frysingere5456972013-06-13 00:07:23 -0400177 cros_build_lib.Die('chroot cannot be in a nosuid mount')
178
Brian Harringae0a5322012-09-15 01:46:51 -0700179 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800180 if chrome_root:
181 cmd.extend(['--chrome_root', chrome_root])
182 if chrome_root_mount:
183 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900184 if goma_dir:
185 cmd.extend(['--goma_dir', goma_dir])
186 if goma_client_json:
187 cmd.extend(['--goma_client_json', goma_client_json])
Joanna Wang1ec0c812021-11-17 17:41:27 -0800188 if reclient_dir:
189 cmd.extend(['--reclient_dir', reclient_dir])
190 if reproxy_cfg_file:
191 cmd.extend(['--reproxy_cfg_file', reproxy_cfg_file])
Yong Hong84ba9172018-02-07 01:37:42 +0800192 if working_dir is not None:
193 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700194
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400195 if additional_args:
Brian Harringb938c782012-02-29 15:14:38 -0800196 cmd.append('--')
197 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700198
Mike Frysinger12d055b2022-06-23 12:26:47 -0400199 if 'CHROMEOS_SUDO_RLIMITS' in os.environ:
200 _SetRlimits(os.environ.pop('CHROMEOS_SUDO_RLIMITS'))
201
Mike Frysingerebb23fc2022-06-06 21:17:39 -0400202 # Some systems set the soft limit too low. Bump it up to the hard limit.
203 # We don't override the hard limit because it's something the admins put
204 # in place and we want to respect such configs. http://b/234353695
205 soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
206 if soft != resource.RLIM_INFINITY and soft < 4096:
207 if soft < hard or hard == resource.RLIM_INFINITY:
208 resource.setrlimit(resource.RLIMIT_NPROC, (hard, hard))
209
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700210 # ThinLTO opens lots of files at the same time.
Bob Haarman7c9f31b2020-10-12 19:08:51 +0000211 # Set rlimit and vm.max_map_count to accommodate this.
212 file_limit = 262144
213 soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
214 resource.setrlimit(resource.RLIMIT_NOFILE,
215 (max(soft, file_limit), max(hard, file_limit)))
Mike Frysinger7201ec52022-06-23 11:12:41 -0400216 max_map_count = int(osutils.ReadFile('/proc/sys/vm/max_map_count'))
Bob Haarman7c9f31b2020-10-12 19:08:51 +0000217 if max_map_count < file_limit:
218 logging.notice(
219 'Raising vm.max_map_count from %s to %s', max_map_count, file_limit)
Mike Frysinger7201ec52022-06-23 11:12:41 -0400220 osutils.WriteFile('/proc/sys/vm/max_map_count', str(file_limit))
Mike Frysingere1407f62021-10-30 01:56:40 -0400221 return cros_build_lib.dbg_run(cmd, check=False)
Brian Harringb938c782012-02-29 15:14:38 -0800222
223
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600224def _ImageFileForChroot(chroot):
225 """Find the image file that should be associated with |chroot|.
226
227 This function does not check if the image exists; it simply returns the
228 filename that would be used.
229
230 Args:
231 chroot: Path to the chroot.
232
233 Returns:
234 Path to an image file that would be associated with chroot.
235 """
236 return chroot.rstrip('/') + '.img'
237
238
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600239def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
240 """Create a snapshot for the specified chroot VG/LV.
241
242 Args:
243 snapshot_name: The name of the new snapshot.
244 chroot_vg: The name of the VG containing the origin LV.
245 chroot_lv: The name of the origin LV.
246
247 Returns:
248 True if the snapshot was created, or False if a snapshot with the same
249 name already exists.
250
251 Raises:
252 SystemExit: The lvcreate command failed.
253 """
254 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
Manoj Guptab12f7302019-06-03 16:40:14 -0700255 logging.error(
256 'Cannot create snapshot %s: A volume with that name already '
257 'exists.', snapshot_name)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600258 return False
259
Manoj Guptab12f7302019-06-03 16:40:14 -0700260 cmd = [
261 'lvcreate', '-s', '--name', snapshot_name,
262 '%s/%s' % (chroot_vg, chroot_lv)
263 ]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600264 try:
265 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
266 chroot_lv, chroot_vg)
Mike Frysinger3e8de442020-02-14 16:46:28 -0500267 cros_build_lib.dbg_run(cmd, capture_output=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600268 return True
Mike Frysinger75634e32020-02-22 23:48:12 -0500269 except cros_build_lib.RunCommandError as e:
270 cros_build_lib.Die('Creating snapshot failed!\n%s', e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600271
272
273def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
274 """Delete the named snapshot from the specified chroot VG.
275
276 If the requested snapshot is not found, nothing happens. The main chroot LV
277 and internal thinpool LV cannot be deleted with this function.
278
279 Args:
280 snapshot_name: The name of the snapshot to delete.
281 chroot_vg: The name of the VG containing the origin LV.
282 chroot_lv: The name of the origin LV.
283
284 Raises:
285 SystemExit: The lvremove command failed.
286 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600287 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
288 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Manoj Guptab12f7302019-06-03 16:40:14 -0700289 logging.error(
290 'Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
291 'if you want to remove the whole chroot.', snapshot_name)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600292 return
293
294 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
295 return
296
297 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
298 try:
299 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
Mike Frysinger3e8de442020-02-14 16:46:28 -0500300 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500301 except cros_build_lib.RunCommandError as e:
302 cros_build_lib.Die('Deleting snapshot failed!\n%s', e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600303
304
305def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
306 """Restore the chroot to an existing snapshot.
307
308 This is done by renaming the original |chroot_lv| LV to a temporary name,
309 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
310 now unused LV. If an error occurs, attempts to rename the original snapshot
311 back to |chroot_lv| to leave the chroot unchanged.
312
313 The chroot must be unmounted before calling this function, and will be left
314 unmounted after this function returns.
315
316 Args:
317 snapshot_name: The name of the snapshot to restore. This snapshot will no
318 longer be accessible at its original name after this function finishes.
319 chroot_vg: The VG containing the chroot LV and snapshot LV.
320 chroot_lv: The name of the original chroot LV.
321
322 Returns:
323 True if the chroot was restored to the requested snapshot, or False if
324 the snapshot wasn't found or isn't valid.
325
326 Raises:
327 SystemExit: Any of the LVM commands failed.
328 """
329 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600330 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
331 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600332 snapshot_name not in valid_snapshots):
333 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
334 snapshot_name, ', '.join(valid_snapshots))
335 return False
336
337 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
338 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
339 try:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500340 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500341 except cros_build_lib.RunCommandError as e:
342 cros_build_lib.Die('Restoring snapshot failed!\n%s', e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600343
344 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
345 try:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500346 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500347 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600348 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
349 try:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500350 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500351 except cros_build_lib.RunCommandError as e:
352 cros_build_lib.Die(
353 'Failed to rename %s to chroot and failed to restore %s back to '
354 'chroot!\n%s', snapshot_name, backup_chroot_name, e)
355 cros_build_lib.Die(
356 'Failed to rename %s to chroot! Original chroot LV has '
357 'been restored.\n%s', snapshot_name, e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600358
359 # Some versions of LVM set snapshots to be skipped at auto-activate time.
360 # Other versions don't have this flag at all. We run lvchange to try
361 # disabling auto-skip and activating the volume, but ignore errors. Versions
362 # that don't have the flag should be auto-activated.
363 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
364 cmd = ['lvchange', '-kn', chroot_lv_path]
Mike Frysinger45602c72019-09-22 02:15:11 -0400365 cros_build_lib.run(
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500366 cmd, print_cmd=False, capture_output=True, check=False)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600367
368 # Activate the LV in case the lvchange above was needed. Activating an LV
369 # that is already active shouldn't do anything, so this is safe to run even if
370 # the -kn wasn't needed.
371 cmd = ['lvchange', '-ay', chroot_lv_path]
Mike Frysinger3e8de442020-02-14 16:46:28 -0500372 cros_build_lib.dbg_run(cmd, capture_output=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600373
374 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
375 try:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500376 cros_build_lib.dbg_run(cmd, capture_output=True)
Mike Frysinger75634e32020-02-22 23:48:12 -0500377 except cros_build_lib.RunCommandError as e:
378 cros_build_lib.Die('Failed to remove backup LV %s/%s!\n%s',
379 chroot_vg, backup_chroot_name, e)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600380
381 return True
382
383
384def ListChrootSnapshots(chroot_vg, chroot_lv):
385 """Return all snapshots in |chroot_vg| regardless of origin volume.
386
387 Args:
388 chroot_vg: The name of the VG containing the chroot.
389 chroot_lv: The name of the chroot LV.
390
391 Returns:
392 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
393
394 Raises:
395 SystemExit: The lvs command failed.
396 """
397 if not chroot_vg or not chroot_lv:
398 return []
399
Manoj Guptab12f7302019-06-03 16:40:14 -0700400 cmd = [
401 'lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name', '--noheadings',
402 '--separator', '\t', chroot_vg
403 ]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600404 try:
Mike Frysinger45602c72019-09-22 02:15:11 -0400405 result = cros_build_lib.run(
Chris McDonaldffdf5aa2020-04-07 16:28:45 -0600406 cmd, print_cmd=False, stdout=True, encoding='utf-8')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600407 except cros_build_lib.RunCommandError:
408 raise SystemExit('Running %r failed!' % cmd)
409
410 # Once the thin origin volume has been deleted, there's no way to tell a
411 # snapshot apart from any other volume. Since this VG is created and managed
412 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
413 # valid snapshots.
414 snapshots = []
415 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
Mike Frysinger876a8e52022-06-23 18:07:30 -0400416 for line in result.stdout.splitlines():
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600417 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
Manoj Guptab12f7302019-06-03 16:40:14 -0700418 if (lv_name == chroot_lv or lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon74645232018-05-04 17:40:42 -0600419 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600420 not snapshot_attrs.match(lv_attr)):
421 continue
422 snapshots.append(lv_name)
423 return snapshots
424
425
Mike Frysinger12d055b2022-06-23 12:26:47 -0400426# The rlimits we will lookup & pass down, in order.
427RLIMITS_TO_PASS = (
428 resource.RLIMIT_AS,
429 resource.RLIMIT_CORE,
430 resource.RLIMIT_CPU,
431 resource.RLIMIT_FSIZE,
432 resource.RLIMIT_MEMLOCK,
433 resource.RLIMIT_NICE,
434 resource.RLIMIT_NOFILE,
435 resource.RLIMIT_NPROC,
436 resource.RLIMIT_RSS,
437 resource.RLIMIT_STACK,
438)
439
440
441def _GetRlimits() -> str:
442 """Serialize current rlimits."""
443 return str(tuple(resource.getrlimit(x) for x in RLIMITS_TO_PASS))
444
445
446def _SetRlimits(limits: str) -> None:
447 """Deserialize rlimits."""
448 for rlim, limit in zip(RLIMITS_TO_PASS, ast.literal_eval(limits)):
449 cur_limit = resource.getrlimit(rlim)
450 if cur_limit != limit:
451 # Turn the number into a symbolic name for logging.
452 name = 'RLIMIT_???'
453 for name, num in resource.__dict__.items():
454 if name.startswith('RLIMIT_') and num == rlim:
455 break
456 logging.debug('Restoring user rlimit %s from %r to %r',
457 name, cur_limit, limit)
458
459 resource.setrlimit(rlim, limit)
460
461
David James56e6c2c2012-10-24 23:54:41 -0700462def _SudoCommand():
463 """Get the 'sudo' command, along with all needed environment variables."""
464
Cindy Lin81093092021-12-09 20:40:57 +0000465 # Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400466 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700467 cmd = ['sudo']
Cindy Lin81093092021-12-09 20:40:57 +0000468 for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700469 value = os.environ.get(key)
470 if value is not None:
471 cmd += ['%s=%s' % (key, value)]
472
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400473 # We keep PATH not for the chroot but for the re-exec & for programs we might
474 # run before we chroot into the SDK. The process that enters the SDK itself
475 # will take care of initializing PATH to the right value then. But we can't
476 # override the system's default PATH for root as that will hide /sbin.
477 cmd += ['CHROMEOS_SUDO_PATH=%s' % os.environ.get('PATH', '')]
478
Mike Frysinger12d055b2022-06-23 12:26:47 -0400479 # Pass along current rlimit settings so we can restore them.
480 cmd += [f'CHROMEOS_SUDO_RLIMITS={_GetRlimits()}']
481
David James56e6c2c2012-10-24 23:54:41 -0700482 # Pass in the path to the depot_tools so that users can access them from
483 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400484 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500485
David James56e6c2c2012-10-24 23:54:41 -0700486 return cmd
487
488
Josh Triplett472a4182013-03-08 11:48:57 -0800489def _ReportMissing(missing):
490 """Report missing utilities, then exit.
491
492 Args:
493 missing: List of missing utilities, as returned by
494 osutils.FindMissingBinaries. If non-empty, will not return.
495 """
496
497 if missing:
498 raise SystemExit(
499 'The tool(s) %s were not found.\n'
500 'Please install the appropriate package in your host.\n'
501 'Example(ubuntu):\n'
Manoj Guptab12f7302019-06-03 16:40:14 -0700502 ' sudo apt-get install <packagename>' % ', '.join(missing))
Josh Triplett472a4182013-03-08 11:48:57 -0800503
504
505def _ProxySimSetup(options):
506 """Set up proxy simulator, and return only in the child environment.
507
508 TODO: Ideally, this should support multiple concurrent invocations of
509 cros_sdk --proxy-sim; currently, such invocations will conflict with each
510 other due to the veth device names and IP addresses. Either this code would
511 need to generate fresh, unused names for all of these before forking, or it
512 would need to support multiple concurrent cros_sdk invocations sharing one
513 proxy and allowing it to exit when unused (without counting on any local
514 service-management infrastructure on the host).
515 """
516
517 may_need_mpm = False
518 apache_bin = osutils.Which('apache2')
519 if apache_bin is None:
520 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
521 if apache_bin is None:
522 _ReportMissing(('apache2',))
523 else:
524 may_need_mpm = True
525
526 # Module names and .so names included for ease of grepping.
527 apache_modules = [('proxy_module', 'mod_proxy.so'),
528 ('proxy_connect_module', 'mod_proxy_connect.so'),
529 ('proxy_http_module', 'mod_proxy_http.so'),
530 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
531
532 # Find the apache module directory, and make sure it has the modules we need.
533 module_dirs = {}
534 for g in PROXY_APACHE_MODULE_GLOBS:
Mike Frysinger336f6b02020-05-09 00:03:28 -0400535 for _, so in apache_modules:
Josh Triplett472a4182013-03-08 11:48:57 -0800536 for f in glob.glob(os.path.join(g, so)):
537 module_dirs.setdefault(os.path.dirname(f), []).append(so)
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400538 for apache_module_path, modules_found in module_dirs.items():
Josh Triplett472a4182013-03-08 11:48:57 -0800539 if len(modules_found) == len(apache_modules):
540 break
541 else:
542 # Appease cros lint, which doesn't understand that this else block will not
543 # fall through to the subsequent code which relies on apache_module_path.
544 apache_module_path = None
545 raise SystemExit(
546 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500547 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800548
549 def check_add_module(name):
550 so = 'mod_%s.so' % name
551 if os.access(os.path.join(apache_module_path, so), os.F_OK):
552 mod = '%s_module' % name
553 apache_modules.append((mod, so))
554 return True
555 return False
556
557 check_add_module('authz_core')
558 if may_need_mpm:
559 for mpm in PROXY_APACHE_MPMS:
560 if check_add_module('mpm_%s' % mpm):
561 break
562
563 veth_host = '%s-host' % PROXY_VETH_PREFIX
564 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
565
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500566 # Set up locks to sync the net namespace setup. We need the child to create
567 # the net ns first, and then have the parent assign the guest end of the veth
568 # interface to the child's new network namespace & bring up the proxy. Only
569 # then can the child move forward and rely on the network being up.
570 ns_create_lock = locking.PipeLock()
571 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800572
573 pid = os.fork()
574 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500575 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800576 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500577
578 # Signal the parent the ns is ready to be configured.
579 ns_create_lock.Post()
580 del ns_create_lock
581
582 # Wait for the parent to finish setting up the ns/proxy.
583 ns_setup_lock.Wait()
584 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800585
586 # Set up child side of the network.
587 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500588 ('ip', 'link', 'set', 'up', 'lo'),
Manoj Guptab12f7302019-06-03 16:40:14 -0700589 ('ip', 'address', 'add', '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500590 'dev', veth_guest),
591 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800592 )
593 try:
594 for cmd in commands:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500595 cros_build_lib.dbg_run(cmd)
Mike Frysinger75634e32020-02-22 23:48:12 -0500596 except cros_build_lib.RunCommandError as e:
597 cros_build_lib.Die('Proxy setup failed!\n%s', e)
Josh Triplett472a4182013-03-08 11:48:57 -0800598
599 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
600 for proto in ('http', 'https', 'ftp'):
601 os.environ[proto + '_proxy'] = proxy_url
602 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
603 os.environ.pop(v, None)
604 return
605
Josh Triplett472a4182013-03-08 11:48:57 -0800606 # Set up parent side of the network.
607 uid = int(os.environ.get('SUDO_UID', '0'))
608 gid = int(os.environ.get('SUDO_GID', '0'))
609 if uid == 0 or gid == 0:
610 for username in PROXY_APACHE_FALLBACK_USERS:
611 try:
612 pwnam = pwd.getpwnam(username)
613 uid, gid = pwnam.pw_uid, pwnam.pw_gid
614 break
615 except KeyError:
616 continue
617 if uid == 0 or gid == 0:
618 raise SystemExit('Could not find a non-root user to run Apache as')
619
620 chroot_parent, chroot_base = os.path.split(options.chroot)
621 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
622 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
623
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500624 # Wait for the child to create the net ns.
625 ns_create_lock.Wait()
626 del ns_create_lock
627
Josh Triplett472a4182013-03-08 11:48:57 -0800628 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500629 'User #%u' % uid,
630 'Group #%u' % gid,
631 'PidFile %s' % pid_file,
632 'ErrorLog %s' % log_file,
633 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
634 'ServerName %s' % PROXY_HOST_IP,
635 'ProxyRequests On',
Mike Frysinger66ce4132019-07-17 22:52:52 -0400636 'AllowCONNECT %s' % ' '.join(str(x) for x in PROXY_CONNECT_PORTS),
Josh Triplett472a4182013-03-08 11:48:57 -0800637 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500638 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
639 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800640 ]
641 commands = (
Manoj Guptab12f7302019-06-03 16:40:14 -0700642 ('ip', 'link', 'add', 'name', veth_host, 'type', 'veth', 'peer', 'name',
643 veth_guest),
644 ('ip', 'address', 'add', '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK), 'dev',
645 veth_host),
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500646 ('ip', 'link', 'set', veth_host, 'up'),
647 ([apache_bin, '-f', '/dev/null'] +
648 [arg for d in apache_directives for arg in ('-C', d)]),
649 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800650 )
Manoj Guptab12f7302019-06-03 16:40:14 -0700651 cmd = None # Make cros lint happy.
Josh Triplett472a4182013-03-08 11:48:57 -0800652 try:
653 for cmd in commands:
Mike Frysinger3e8de442020-02-14 16:46:28 -0500654 cros_build_lib.dbg_run(cmd)
Mike Frysinger75634e32020-02-22 23:48:12 -0500655 except cros_build_lib.RunCommandError as e:
Josh Triplett472a4182013-03-08 11:48:57 -0800656 # Clean up existing interfaces, if any.
657 cmd_cleanup = ('ip', 'link', 'del', veth_host)
658 try:
Mike Frysinger45602c72019-09-22 02:15:11 -0400659 cros_build_lib.run(cmd_cleanup, print_cmd=False)
Josh Triplett472a4182013-03-08 11:48:57 -0800660 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700661 logging.error('running %r failed', cmd_cleanup)
Mike Frysinger75634e32020-02-22 23:48:12 -0500662 cros_build_lib.Die('Proxy network setup failed!\n%s', e)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500663
664 # Signal the child that the net ns/proxy is fully configured now.
665 ns_setup_lock.Post()
666 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800667
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400668 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800669
670
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400671def _BuildReExecCommand(argv, opts) -> List[str]:
672 """Generate new command for self-reexec."""
673 # Make sure to preserve the active Python executable in case the version
674 # we're running as is not the default one found via the (new) $PATH.
675 cmd = _SudoCommand() + ['--']
676 if opts.strace:
677 cmd += ['strace'] + shlex.split(opts.strace_arguments) + ['--']
678 return cmd + [sys.executable] + argv
679
680
681def _ReExecuteIfNeeded(argv, opts):
David James56e6c2c2012-10-24 23:54:41 -0700682 """Re-execute cros_sdk as root.
683
684 Also unshare the mount namespace so as to ensure that processes outside
685 the chroot can't mess with our mounts.
686 """
Ram Chandrasekar69751282022-02-25 21:07:36 +0000687 if osutils.IsNonRootUser():
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400688 cmd = _BuildReExecCommand(argv, opts)
Mike Frysinger3e8de442020-02-14 16:46:28 -0500689 logging.debug('Reexecing self via sudo:\n%s', cros_build_lib.CmdToStr(cmd))
Mike Frysingera78a56e2012-11-20 06:02:30 -0500690 os.execvp(cmd[0], cmd)
David James56e6c2c2012-10-24 23:54:41 -0700691
692
Mike Frysinger34db8692013-11-11 14:54:08 -0500693def _CreateParser(sdk_latest_version, bootstrap_latest_version):
694 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400695 usage = ('usage: %(prog)s [options] '
696 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
Manoj Guptab12f7302019-06-03 16:40:14 -0700697 parser = commandline.ArgumentParser(
698 usage=usage, description=__doc__, caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700699
Mike Frysinger34db8692013-11-11 14:54:08 -0500700 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500701 default_chroot = os.path.join(constants.SOURCE_ROOT,
702 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400703 parser.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700704 '--chroot',
705 dest='chroot',
706 default=default_chroot,
707 type='path',
Brian Harring218e13c2012-10-10 16:21:26 -0700708 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Manoj Guptab12f7302019-06-03 16:40:14 -0700709 parser.add_argument(
710 '--nouse-image',
711 dest='use_image',
712 action='store_false',
Benjamin Gordon87b068a2020-11-02 11:22:16 -0700713 default=False,
Manoj Guptab12f7302019-06-03 16:40:14 -0700714 help='Do not mount the chroot on a loopback image; '
715 'instead, create it directly in a directory.')
Benjamin Gordonacbaac22020-09-25 12:59:33 -0600716 parser.add_argument(
717 '--use-image',
718 dest='use_image',
719 action='store_true',
Benjamin Gordon87b068a2020-11-02 11:22:16 -0700720 default=False,
Benjamin Gordonacbaac22020-09-25 12:59:33 -0600721 help='Mount the chroot on a loopback image '
722 'instead of creating it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800723
Manoj Guptab12f7302019-06-03 16:40:14 -0700724 parser.add_argument(
Alex Klein5e4b1bc2019-07-02 12:27:06 -0600725 '--chrome-root',
Manoj Guptab12f7302019-06-03 16:40:14 -0700726 '--chrome_root',
727 type='path',
728 help='Mount this chrome root into the SDK chroot')
729 parser.add_argument(
730 '--chrome_root_mount',
731 type='path',
732 help='Mount chrome into this path inside SDK chroot')
733 parser.add_argument(
734 '--nousepkg',
735 action='store_true',
736 default=False,
737 help='Do not use binary packages when creating a chroot.')
738 parser.add_argument(
739 '-u',
740 '--url',
741 dest='sdk_url',
742 help='Use sdk tarball located at this url. Use file:// '
743 'for local files.')
744 parser.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700745 '--sdk-version',
746 help=('Use this sdk version. For prebuilt, current is %r'
747 ', for bootstrapping it is %r.' % (sdk_latest_version,
748 bootstrap_latest_version)))
749 parser.add_argument(
750 '--goma_dir',
751 type='path',
752 help='Goma installed directory to mount into the chroot.')
753 parser.add_argument(
754 '--goma_client_json',
755 type='path',
756 help='Service account json file to use goma on bot. '
757 'Mounted into the chroot.')
Joanna Wang1ec0c812021-11-17 17:41:27 -0800758 parser.add_argument(
759 '--reclient-dir',
760 type='path',
761 help='Reclient installed directory to mount into the chroot.')
762 parser.add_argument(
763 '--reproxy-cfg-file',
764 type='path',
765 help="Config file for re-client's reproxy used for remoteexec.")
Sergey Frolov6038f612022-06-13 14:07:21 -0600766 parser.add_argument(
767 '--skip-chroot-upgrade',
768 dest='chroot_upgrade',
769 action='store_false',
770 default=True,
771 help='Skip automatic SDK and toolchain upgrade when entering the chroot. '
772 'Never guaranteed to work, especially as ToT moves forward.')
Yong Hong84ba9172018-02-07 01:37:42 +0800773
774 # Use type=str instead of type='path' to prevent the given path from being
775 # transfered to absolute path automatically.
Manoj Guptab12f7302019-06-03 16:40:14 -0700776 parser.add_argument(
777 '--working-dir',
778 type=str,
779 help='Run the command in specific working directory in '
780 'chroot. If the given directory is a relative '
781 'path, this program will transfer the path to '
782 'the corresponding one inside chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800783
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400784 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500785
786 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400787 group = parser.add_argument_group('Commands')
788 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700789 '--enter',
790 action='store_true',
791 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500792 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400793 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700794 '--create',
795 action='store_true',
796 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500797 help='Create the chroot only if it does not already exist. '
798 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400799 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700800 '--bootstrap',
801 action='store_true',
802 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500803 help='Build everything from scratch, including the sdk. '
804 'Use this only if you need to validate a change '
805 'that affects SDK creation itself (toolchain and '
806 'build are typically the only folk who need this). '
807 'Note this will quite heavily slow down the build. '
808 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400809 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700810 '-r',
811 '--replace',
812 action='store_true',
813 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500814 help='Replace an existing SDK chroot. Basically an alias '
815 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400816 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700817 '--delete',
818 action='store_true',
819 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500820 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400821 group.add_argument(
Michael Mortensene979a4d2020-06-24 13:09:42 -0600822 '--force',
823 action='store_true',
824 default=False,
825 help='Force unmount/delete of the current SDK chroot even if '
826 'obtaining the write lock fails.')
827 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700828 '--unmount',
829 action='store_true',
830 default=False,
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600831 help='Unmount and clean up devices associated with the '
832 'SDK chroot if it exists. This does not delete the '
833 'backing image file, so the same chroot can be later '
834 're-mounted for reuse. To fully delete the chroot, use '
835 '--delete. This is primarily useful for working on '
836 'cros_sdk or the chroot setup; you should not need it '
837 'under normal circumstances.')
838 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700839 '--download',
840 action='store_true',
841 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500842 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600843 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700844 '--snapshot-create',
845 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600846 help='Create a snapshot of the chroot. Requires that the chroot was '
Manoj Guptab12f7302019-06-03 16:40:14 -0700847 'created without the --nouse-image option.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600848 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700849 '--snapshot-restore',
850 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600851 help='Restore the chroot to a previously created snapshot.')
852 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700853 '--snapshot-delete',
854 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600855 help='Delete a previously created snapshot. Deleting a snapshot that '
Manoj Guptab12f7302019-06-03 16:40:14 -0700856 'does not exist is not an error.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600857 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700858 '--snapshot-list',
859 action='store_true',
860 default=False,
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600861 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500862 commands = group
863
Mike Frysinger80dfce92014-04-21 10:58:53 -0400864 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400865 group = parser.add_argument_group('Namespaces')
Manoj Guptab12f7302019-06-03 16:40:14 -0700866 group.add_argument(
867 '--proxy-sim',
868 action='store_true',
869 default=False,
870 help='Simulate a restrictive network requiring an outbound'
871 ' proxy.')
Mike Frysinger79024a32021-04-05 03:28:35 -0400872 for ns, default in (('pid', True), ('net', None)):
873 group.add_argument(
874 f'--ns-{ns}',
875 default=default,
876 action='store_true',
877 help=f'Create a new {ns} namespace.')
878 group.add_argument(
879 f'--no-ns-{ns}',
880 dest=f'ns_{ns}',
881 action='store_false',
882 help=f'Do not create a new {ns} namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400883
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400884 # Debug options.
885 group = parser.debug_group
886 group.add_argument(
887 '--strace',
888 action='store_true',
889 help='Run cros_sdk through strace after re-exec via sudo')
890 group.add_argument(
891 '--strace-arguments',
892 default='',
893 help='Extra strace options (shell quoting permitted)')
894
Mike Frysinger34db8692013-11-11 14:54:08 -0500895 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400896 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500897 'Internal Chromium OS Build Team Options',
898 'Caution: these are for meant for the Chromium OS build team only')
Manoj Guptab12f7302019-06-03 16:40:14 -0700899 group.add_argument(
900 '--buildbot-log-version',
901 default=False,
902 action='store_true',
903 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500904
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400905 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500906
907
908def main(argv):
Greg Edelston97f4e302020-03-13 14:01:23 -0600909 # Turn on strict sudo checks.
910 cros_build_lib.STRICT_SUDO = True
Mike Frysingere652ba12019-09-08 00:57:43 -0400911 conf = key_value_store.LoadFile(
Mike Frysinger34db8692013-11-11 14:54:08 -0500912 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
913 ignore_missing=True)
914 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
Manoj Gupta01927c12019-05-13 17:33:14 -0700915 bootstrap_frozen_version = conf.get('BOOTSTRAP_FROZEN_VERSION', '<unknown>')
Manoj Gupta55a63092019-06-13 11:47:13 -0700916
917 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
918 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
919 bootstrap_latest_version = (
920 sdk_latest_version
921 if bootstrap_frozen_version == '<unknown>' else bootstrap_frozen_version)
Mike Frysinger34db8692013-11-11 14:54:08 -0500922 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400923 options = parser.parse_args(argv)
924 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800925
Sloan Johnsonc59e9262022-06-10 20:51:53 +0000926 # Some basic checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500927 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800928
Brian Harring1790ac42012-09-23 08:53:33 -0700929 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700930 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600931 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700932 "cros_sdk is currently only supported on x86_64; you're running"
Mike Frysinger80de5012019-08-01 14:10:53 -0400933 ' %s. Please find a x86_64 machine.' % (host,))
Brian Harring1790ac42012-09-23 08:53:33 -0700934
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400935 # Merge the outside PATH setting if we re-execed ourselves.
936 if 'CHROMEOS_SUDO_PATH' in os.environ:
937 os.environ['PATH'] = '%s:%s' % (os.environ.pop('CHROMEOS_SUDO_PATH'),
938 os.environ['PATH'])
939
Josh Triplett472a4182013-03-08 11:48:57 -0800940 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
941 if options.proxy_sim:
942 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600943 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800944
Benjamin Gordon040a1162017-06-29 13:44:47 -0600945 if (sdk_latest_version == '<unknown>' or
946 bootstrap_latest_version == '<unknown>'):
947 cros_build_lib.Die(
948 'No SDK version was found. '
949 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
950 'Please change to a directory inside your Chromium OS source tree\n'
951 'and retry. If you need to setup a Chromium OS source tree, see\n'
Mike Frysingerdcad4e02018-08-03 16:20:02 -0400952 ' https://dev.chromium.org/chromium-os/developer-guide')
Benjamin Gordon040a1162017-06-29 13:44:47 -0600953
Manoj Guptab12f7302019-06-03 16:40:14 -0700954 any_snapshot_operation = (
955 options.snapshot_create or options.snapshot_restore or
956 options.snapshot_delete or options.snapshot_list)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600957
Manoj Guptab12f7302019-06-03 16:40:14 -0700958 if (options.snapshot_delete and
959 options.snapshot_delete == options.snapshot_restore):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600960 parser.error('Cannot --snapshot_delete the same snapshot you are '
961 'restoring with --snapshot_restore.')
962
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400963 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -0800964
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600965 lock_path = os.path.dirname(options.chroot)
966 lock_path = os.path.join(
967 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
968
Brian Harring218e13c2012-10-10 16:21:26 -0700969 # Expand out the aliases...
970 if options.replace:
971 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800972
Brian Harring218e13c2012-10-10 16:21:26 -0700973 if options.bootstrap:
974 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800975
Brian Harring218e13c2012-10-10 16:21:26 -0700976 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400977 # pylint: disable=protected-access
978 # This _group_actions access sucks, but upstream decided to not include an
979 # alternative to optparse's option_list, and this is what they recommend.
Manoj Guptab12f7302019-06-03 16:40:14 -0700980 options.enter |= not any(
981 getattr(options, x.dest) for x in commands._group_actions)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400982 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700983 options.enter |= bool(chroot_command)
984
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600985 if (options.delete and not options.create and
986 (options.enter or any_snapshot_operation)):
Mike Frysinger80de5012019-08-01 14:10:53 -0400987 parser.error('Trying to enter or snapshot the chroot when --delete '
988 'was specified makes no sense.')
Brian Harring218e13c2012-10-10 16:21:26 -0700989
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600990 if (options.unmount and
991 (options.create or options.enter or any_snapshot_operation)):
992 parser.error('--unmount cannot be specified with other chroot actions.')
993
Yong Hong84ba9172018-02-07 01:37:42 +0800994 if options.working_dir is not None and not os.path.isabs(options.working_dir):
995 options.working_dir = path_util.ToChrootPath(options.working_dir)
996
Benjamin Gordon87b068a2020-11-02 11:22:16 -0700997 # If there is an existing chroot image and we're not removing it then force
998 # use_image on. This ensures that people don't have to remember to pass
999 # --use-image after a reboot to avoid losing access to their existing chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001000 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordon87b068a2020-11-02 11:22:16 -07001001 img_path = _ImageFileForChroot(options.chroot)
Benjamin Gordon832a4412020-12-08 10:39:16 -07001002 if (not options.use_image and not options.delete and not options.unmount
1003 and os.path.exists(img_path)):
1004 if chroot_exists:
1005 # If the chroot is already populated, make sure it has something
1006 # mounted on it before we assume it came from an image.
1007 cmd = ['mountpoint', '-q', options.chroot]
1008 if cros_build_lib.dbg_run(cmd, check=False).returncode == 0:
1009 options.use_image = True
1010
1011 else:
1012 logging.notice('Existing chroot image %s found. Forcing --use-image on.',
1013 img_path)
1014 options.use_image = True
Benjamin Gordon87b068a2020-11-02 11:22:16 -07001015
Benjamin Gordon9bce7032020-11-19 09:58:44 -07001016 if any_snapshot_operation and not options.use_image:
1017 if os.path.exists(img_path):
1018 options.use_image = True
1019 else:
1020 cros_build_lib.Die('Snapshot operations are not compatible with '
1021 '--nouse-image.')
1022
Benjamin Gordon87b068a2020-11-02 11:22:16 -07001023 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001024 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001025 not options.unmount and not missing_image_tools and
Benjamin Gordon87b068a2020-11-02 11:22:16 -07001026 os.path.exists(img_path)):
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001027 # Try to re-mount an existing image in case the user has rebooted.
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001028 with locking.FileLock(lock_path, 'chroot lock') as lock:
1029 logging.debug('Checking if existing chroot image can be mounted.')
1030 lock.write_lock()
1031 cros_sdk_lib.MountChroot(options.chroot, create=False)
1032 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
1033 if chroot_exists:
1034 logging.notice('Mounted existing image %s on chroot', img_path)
Brian Harring218e13c2012-10-10 16:21:26 -07001035
1036 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001037 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -07001038 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -08001039
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001040 # Make sure we will download if we plan to create.
1041 options.download |= options.create
1042
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001043 # Anything that needs to manipulate the main chroot mount or communicate with
1044 # LVM needs to be done here before we enter the new namespaces.
1045
1046 # If deleting, do it regardless of the use_image flag so that a
1047 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001048 if options.delete:
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001049 # Set a timeout of 300 seconds when getting the lock.
1050 with locking.FileLock(lock_path, 'chroot lock',
1051 blocking_timeout=300) as lock:
1052 try:
1053 lock.write_lock()
1054 except timeout_util.TimeoutError as e:
1055 logging.error('Acquiring write_lock on %s failed: %s', lock_path, e)
1056 if not options.force:
1057 cros_build_lib.Die('Exiting; use --force to continue w/o lock.')
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001058 else:
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001059 logging.warning(
1060 'cros_sdk was invoked with force option, continuing.')
Mike Frysingerf6fe6d02021-06-16 20:26:35 -04001061 logging.notice('Deleting chroot.')
1062 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001063
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001064 # If cleanup was requested, we have to do it while we're still in the original
1065 # namespace. Since cleaning up the mount will interfere with any other
1066 # commands, we exit here. The check above should have made sure that no other
1067 # action was requested, anyway.
1068 if options.unmount:
Michael Mortensenbf296fb2020-06-18 18:21:54 -06001069 # Set a timeout of 300 seconds when getting the lock.
1070 with locking.FileLock(lock_path, 'chroot lock',
1071 blocking_timeout=300) as lock:
1072 try:
1073 lock.write_lock()
1074 except timeout_util.TimeoutError as e:
1075 logging.error('Acquiring write_lock on %s failed: %s', lock_path, e)
Michael Mortensen1a176922020-07-14 20:53:35 -06001076 logging.warning(
1077 'Continuing with CleanupChroot(%s), which will umount the tree.',
1078 options.chroot)
Michael Mortensenbf296fb2020-06-18 18:21:54 -06001079 # We can call CleanupChroot (which calls cros_sdk_lib.CleanupChrootMount)
1080 # even if we don't get the lock because it will attempt to unmount the
1081 # tree and will print diagnostic information from 'fuser', 'lsof', and
1082 # 'ps'.
Mike Frysingerf6fe6d02021-06-16 20:26:35 -04001083 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=False)
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -06001084 sys.exit(0)
1085
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001086 # Make sure the main chroot mount is visible. Contents will be filled in
1087 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001088 if options.create and options.use_image:
1089 if missing_image_tools:
Mike Frysinger80de5012019-08-01 14:10:53 -04001090 raise SystemExit("""The tool(s) %s were not found.
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001091Please make sure the lvm2 and thin-provisioning-tools packages
1092are installed on your host.
1093Example(ubuntu):
1094 sudo apt-get install lvm2 thin-provisioning-tools
1095
1096If you want to run without lvm2, pass --nouse-image (chroot
Mike Frysinger80de5012019-08-01 14:10:53 -04001097snapshots will be unavailable).""" % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001098
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001099 logging.debug('Making sure chroot image is mounted.')
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001100 with locking.FileLock(lock_path, 'chroot lock') as lock:
1101 lock.write_lock()
1102 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
1103 cros_build_lib.Die('Unable to mount %s on chroot',
1104 _ImageFileForChroot(options.chroot))
1105 logging.notice('Mounted %s on chroot',
1106 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001107
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001108 # Snapshot operations will always need the VG/LV, but other actions won't.
1109 if any_snapshot_operation:
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001110 with locking.FileLock(lock_path, 'chroot lock') as lock:
1111 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(options.chroot)
1112 if not chroot_vg or not chroot_lv:
1113 cros_build_lib.Die('Unable to find VG/LV for chroot %s', options.chroot)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001114
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001115 # Delete snapshot before creating a new one. This allows the user to
1116 # throw out old state, create a new snapshot, and enter the chroot in a
1117 # single call to cros_sdk. Since restore involves deleting, also do it
1118 # before creating.
1119 if options.snapshot_restore:
1120 lock.write_lock()
1121 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1122 if options.snapshot_restore not in valid_snapshots:
1123 cros_build_lib.Die(
1124 '%s is not a valid snapshot to restore to. Valid snapshots: %s',
1125 options.snapshot_restore, ', '.join(valid_snapshots))
1126 osutils.UmountTree(options.chroot)
1127 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1128 chroot_lv):
1129 cros_build_lib.Die('Unable to restore chroot to snapshot.')
1130 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
1131 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001132
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001133 # Use a read lock for snapshot delete and create even though they modify
1134 # the filesystem, because they don't modify the mounted chroot itself.
1135 # The underlying LVM commands take their own locks, so conflicting
1136 # concurrent operations here may crash cros_sdk, but won't corrupt the
1137 # chroot image. This tradeoff seems worth it to allow snapshot
1138 # operations on chroots that have a process inside.
1139 if options.snapshot_delete:
1140 lock.read_lock()
1141 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001142
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001143 if options.snapshot_create:
1144 lock.read_lock()
1145 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1146 chroot_lv):
1147 cros_build_lib.Die('Unable to create snapshot.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001148
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001149 img_path = _ImageFileForChroot(options.chroot)
1150 if (options.use_image and os.path.exists(options.chroot) and
1151 os.path.exists(img_path)):
1152 img_stat = os.stat(img_path)
1153 img_used_bytes = img_stat.st_blocks * 512
1154
1155 mount_stat = os.statvfs(options.chroot)
Manoj Guptab12f7302019-06-03 16:40:14 -07001156 mount_used_bytes = mount_stat.f_frsize * (
1157 mount_stat.f_blocks - mount_stat.f_bfree)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001158
Mike Frysinger93e8ffa2019-07-03 20:24:18 -04001159 extra_gbs = (img_used_bytes - mount_used_bytes) // 2**30
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001160 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1161 logging.notice('%s is using %s GiB more than needed. Running '
Sergey Frolov1cb46ec2020-12-09 21:46:16 -07001162 'fstrim in background.', img_path, extra_gbs)
1163 pid = os.fork()
1164 if pid == 0:
1165 try:
1166 # Directly call Popen to run fstrim concurrently.
1167 cmd = ['fstrim', options.chroot]
1168 subprocess.Popen(cmd, close_fds=True, shell=False)
1169 except subprocess.SubprocessError as e:
1170 logging.warning(
1171 'Running fstrim failed. Consider running fstrim on '
1172 'your chroot manually.\n%s', e)
1173 os._exit(0) # pylint: disable=protected-access
1174 os.waitpid(pid, 0)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001175
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001176 # Enter a new set of namespaces. Everything after here cannot directly affect
1177 # the hosts's mounts or alter LVM volumes.
Mike Frysinger79024a32021-04-05 03:28:35 -04001178 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001179
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001180 if options.snapshot_list:
1181 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1182 print(snap)
1183 sys.exit(0)
1184
Brian Harringb938c782012-02-29 15:14:38 -08001185 if not options.sdk_version:
Manoj Guptab12f7302019-06-03 16:40:14 -07001186 sdk_version = (
1187 bootstrap_latest_version if options.bootstrap else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001188 else:
1189 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001190 if options.buildbot_log_version:
Chris McDonaldb55b7032021-06-17 16:41:32 -06001191 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001192
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001193 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001194 if options.download:
1195 if options.sdk_url:
1196 urls = [options.sdk_url]
Yong Hong4e29b622018-02-05 14:31:10 +08001197 else:
1198 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001199
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001200 with locking.FileLock(lock_path, 'chroot lock') as lock:
1201 if options.proxy_sim:
1202 _ProxySimSetup(options)
Josh Triplett472a4182013-03-08 11:48:57 -08001203
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001204 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1205 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
1206 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001207
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001208 for target in (sdk_cache, distfiles_cache):
1209 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
1210 if not os.path.exists(src):
1211 osutils.SafeMakedirsNonRoot(target)
1212 continue
1213 lock.write_lock(
1214 'Upgrade to %r needed but chroot is locked; please exit '
1215 'all instances so this upgrade can finish.' % src)
1216 if not os.path.exists(src):
1217 # Note that while waiting for the write lock, src may've vanished;
1218 # it's a rare race during the upgrade process that's a byproduct
1219 # of us avoiding taking a write lock to do the src check. If we
1220 # took a write lock for that check, it would effectively limit
1221 # all cros_sdk for a chroot to a single instance.
1222 osutils.SafeMakedirsNonRoot(target)
1223 elif not os.path.exists(target):
1224 # Upgrade occurred, but a reversion, or something whacky
1225 # occurred writing to the old location. Wipe and continue.
1226 os.rename(src, target)
1227 else:
1228 # Upgrade occurred once already, but either a reversion or
1229 # some before/after separate cros_sdk usage is at play.
1230 # Wipe and continue.
1231 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001232
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001233 if options.download:
1234 lock.write_lock()
Mike Frysingerf744d032022-05-07 20:38:39 -04001235 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -07001236
Mike Frysinger65b7b242021-06-17 21:11:25 -04001237 mounted = False
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001238 if options.create:
1239 lock.write_lock()
1240 # Recheck if the chroot is set up here before creating to make sure we
1241 # account for whatever the various delete/unmount/remount steps above
1242 # have done.
1243 if cros_sdk_lib.IsChrootReady(options.chroot):
1244 logging.debug('Chroot already exists. Skipping creation.')
1245 else:
Mike Frysinger23b5cf52021-06-16 23:18:00 -04001246 cros_sdk_lib.CreateChroot(
1247 Path(options.chroot),
1248 Path(sdk_tarball),
1249 Path(options.cache_dir),
Sergey Frolov6038f612022-06-13 14:07:21 -06001250 usepkg=not options.bootstrap and not options.nousepkg,
1251 chroot_upgrade=options.chroot_upgrade)
Mike Frysinger65b7b242021-06-17 21:11:25 -04001252 mounted = True
Brian Harring1790ac42012-09-23 08:53:33 -07001253
Mike Frysingerbf47cce2021-01-20 13:46:30 -05001254 if options.enter:
1255 lock.read_lock()
Mike Frysinger65b7b242021-06-17 21:11:25 -04001256 if not mounted:
1257 cros_sdk_lib.MountChrootPaths(options.chroot)
Mike Frysingere1407f62021-10-30 01:56:40 -04001258 ret = EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
1259 options.chrome_root_mount, options.goma_dir,
Joanna Wang1ec0c812021-11-17 17:41:27 -08001260 options.goma_client_json, options.reclient_dir,
1261 options.reproxy_cfg_file, options.working_dir,
Mike Frysingere1407f62021-10-30 01:56:40 -04001262 chroot_command)
1263 sys.exit(ret.returncode)