blob: cf7db9654d290352b9949f7e77ee89e0ad80c875 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysinger2de7f042012-07-10 04:45:03 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Manoj Gupta7fad04d2019-06-14 20:12:25 -07005
Mike Frysinger2f95cfc2015-06-04 04:00:26 -04006"""Manage SDK chroots.
7
8This script is used for manipulating local chroot environments; creating,
9deleting, downloading, etc. If given --enter (or no args), it defaults
10to an interactive bash shell within the chroot.
11
12If given args those are passed to the chroot environment, and executed.
13"""
Brian Harringb938c782012-02-29 15:14:38 -080014
Mike Frysinger383367e2014-09-16 15:06:17 -040015from __future__ import print_function
16
Mike Frysinger2f95cfc2015-06-04 04:00:26 -040017import argparse
Josh Triplett472a4182013-03-08 11:48:57 -080018import glob
Brian Harringb938c782012-02-29 15:14:38 -080019import os
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
David James56e6c2c2012-10-24 23:54:41 -070024import sys
Brian Harringb938c782012-02-29 15:14:38 -080025import urlparse
26
Aviv Keshetb7519e12016-10-04 00:50:00 -070027from chromite.lib import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080028from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070029from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080030from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070031from chromite.lib import cros_logging as logging
Benjamin Gordon74645232018-05-04 17:40:42 -060032from chromite.lib import cros_sdk_lib
Brian Harringb938c782012-02-29 15:14:38 -080033from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080034from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070035from chromite.lib import osutils
Yong Hong84ba9172018-02-07 01:37:42 +080036from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040037from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080038from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050039from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080040
41cros_build_lib.STRICT_SUDO = True
42
Zdenek Behanaa52cea2012-05-30 01:31:11 +020043COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020044
Brian Harringb938c782012-02-29 15:14:38 -080045# TODO(zbehan): Remove the dependency on these, reimplement them in python
Manoj Guptab12f7302019-06-03 16:40:14 -070046MAKE_CHROOT = [
47 os.path.join(constants.SOURCE_ROOT, 'src/scripts/sdk_lib/make_chroot.sh')
48]
49ENTER_CHROOT = [
50 os.path.join(constants.SOURCE_ROOT, 'src/scripts/sdk_lib/enter_chroot.sh')
51]
Brian Harringb938c782012-02-29 15:14:38 -080052
Josh Triplett472a4182013-03-08 11:48:57 -080053# Proxy simulator configuration.
54PROXY_HOST_IP = '192.168.240.1'
55PROXY_PORT = 8080
56PROXY_GUEST_IP = '192.168.240.2'
57PROXY_NETMASK = 30
58PROXY_VETH_PREFIX = 'veth'
59PROXY_CONNECT_PORTS = (80, 443, 9418)
60PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
61PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
62PROXY_APACHE_FALLBACK_PATH = ':'.join(
Manoj Guptab12f7302019-06-03 16:40:14 -070063 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS)
Josh Triplett472a4182013-03-08 11:48:57 -080064PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
65
Josh Triplett9a495f62013-03-15 18:06:55 -070066# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080067NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080068
Josh Triplett472a4182013-03-08 11:48:57 -080069# Tools needed for --proxy-sim only.
70PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080071
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060072# Tools needed when use_image is true (the default).
73IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060074 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060075
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070076# As space is used inside the chroot, the empty space in chroot.img is
77# allocated. Deleting files inside the chroot doesn't automatically return the
78# used space to the OS. Over time, this tends to make the sparse chroot.img
79# less sparse even if the chroot contents don't currently need much space. We
80# can recover most of this unused space with fstrim, but that takes too much
81# time to run it every time. Instead, check the used space against the image
82# size after mounting the chroot and only call fstrim if it looks like we could
83# recover at least this many GiB.
84MAX_UNUSED_IMAGE_GBS = 20
85
Mike Frysingercc838832014-05-24 13:10:30 -040086
Brian Harring1790ac42012-09-23 08:53:33 -070087def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080088 """Returns the URL for a given arch/version"""
Manoj Guptab12f7302019-06-03 16:40:14 -070089 extension = {'bz2': 'tbz2', 'xz': 'tar.xz'}
90 return [
91 toolchain.GetSdkURL(
92 suburl='cros-sdk-%s.%s' % (version, extension[compressor]))
93 for compressor in COMPRESSION_PREFERENCE
94 ]
Brian Harring1790ac42012-09-23 08:53:33 -070095
96
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070097def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -050098 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020099
100 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500101 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200102 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700103 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700104 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200105
106 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700107 Full path to the downloaded file, or None if |allow_none| and no URL worked.
108
109 Raises:
110 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112
Brian Harring1790ac42012-09-23 08:53:33 -0700113 # Note we track content length ourselves since certain versions of curl
114 # fail if asked to resume a complete file.
Brian Harring1790ac42012-09-23 08:53:33 -0700115 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700116 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700117 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Mike Frysinger27e21b72018-07-12 14:20:21 -0400118 # pylint: disable=undefined-loop-variable
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200119 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700120 parsed = urlparse.urlparse(url)
121 tarball_name = os.path.basename(parsed.path)
122 if parsed.scheme in ('', 'file'):
123 if os.path.exists(parsed.path):
124 return parsed.path
125 continue
126 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700127 logging.debug('Attempting download from %s', url)
Manoj Guptab12f7302019-06-03 16:40:14 -0700128 result = retry_util.RunCurl(['-I', url],
129 print_cmd=False,
130 debug_level=logging.NOTICE,
131 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700132 successful = False
133 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700134 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700135 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700136 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700137 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500138 elif header.lower().startswith('content-length:'):
139 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700140 if successful:
141 break
142 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200143 break
144 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700145 if allow_none:
146 return None
147 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200148
Brian Harringae0a5322012-09-15 01:46:51 -0700149 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700150 current_size = 0
151 if os.path.exists(tarball_dest):
152 current_size = os.path.getsize(tarball_dest)
153 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700154 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700155 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100156
Brian Harring1790ac42012-09-23 08:53:33 -0700157 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800158 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900159 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Manoj Guptab12f7302019-06-03 16:40:14 -0700160 print_cmd=False,
161 debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800162
Brian Harring1790ac42012-09-23 08:53:33 -0700163 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700164 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
165 # because we might have partial overlap between known prefixes.
166 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
167 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
168 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700169 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700170 if (filename == tarball_name or
171 any([(filename.startswith(p) and
172 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
173 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700174 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700175 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700176 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200177
Brian Harringb938c782012-02-29 15:14:38 -0800178 return tarball_dest
179
180
Benjamin Gordon589873b2018-05-31 14:30:56 -0600181def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600182 """Creates a new chroot from a given SDK.
183
184 Args:
185 chroot_path: Path where the new chroot will be created.
186 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600187 cache_dir: Path to a directory that will be used for caching portage files,
188 etc.
189 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
190 chroot.
191 """
Brian Harringb938c782012-02-29 15:14:38 -0800192
Manoj Guptab12f7302019-06-03 16:40:14 -0700193 cmd = MAKE_CHROOT + [
194 '--stage3_path', sdk_tarball, '--chroot', chroot_path, '--cache_dir',
195 cache_dir
196 ]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700197
Mike Frysinger2de7f042012-07-10 04:45:03 -0400198 if nousepkg:
199 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800200
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700201 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800202 try:
203 cros_build_lib.RunCommand(cmd, print_cmd=False)
204 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700205 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800206
207
208def DeleteChroot(chroot_path):
209 """Deletes an existing chroot"""
Manoj Guptab12f7302019-06-03 16:40:14 -0700210 cmd = MAKE_CHROOT + ['--chroot', chroot_path, '--delete']
Brian Harringb938c782012-02-29 15:14:38 -0800211 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700212 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800213 cros_build_lib.RunCommand(cmd, print_cmd=False)
214 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700215 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800216
217
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600218def CleanupChroot(chroot_path):
219 """Unmounts a chroot and cleans up any associated devices."""
Don Garrett36650112018-06-28 15:54:34 -0700220 cros_sdk_lib.CleanupChrootMount(chroot_path, delete=False)
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600221
222
Brian Harringae0a5322012-09-15 01:46:51 -0700223def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Mike Frysinger0b2d9ee2019-02-28 17:05:47 -0500224 goma_dir, goma_client_json, working_dir, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800225 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400226 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
227 # The os.ST_NOSUID constant wasn't added until python-3.2.
228 if st.f_flag & 0x2:
229 cros_build_lib.Die('chroot cannot be in a nosuid mount')
230
Brian Harringae0a5322012-09-15 01:46:51 -0700231 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800232 if chrome_root:
233 cmd.extend(['--chrome_root', chrome_root])
234 if chrome_root_mount:
235 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900236 if goma_dir:
237 cmd.extend(['--goma_dir', goma_dir])
238 if goma_client_json:
239 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800240 if working_dir is not None:
241 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700242
Brian Harringb938c782012-02-29 15:14:38 -0800243 if len(additional_args) > 0:
244 cmd.append('--')
245 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700246
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700247 # ThinLTO opens lots of files at the same time.
248 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Manoj Guptab12f7302019-06-03 16:40:14 -0700249 ret = cros_build_lib.RunCommand(
250 cmd, print_cmd=False, error_code_ok=True, mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700251 # If we were in interactive mode, ignore the exit code; it'll be whatever
252 # they last ran w/in the chroot and won't matter to us one way or another.
253 # Note this does allow chroot entrance to fail and be ignored during
254 # interactive; this is however a rare case and the user will immediately
255 # see it (nor will they be checking the exit code manually).
256 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700257 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800258
259
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600260def _ImageFileForChroot(chroot):
261 """Find the image file that should be associated with |chroot|.
262
263 This function does not check if the image exists; it simply returns the
264 filename that would be used.
265
266 Args:
267 chroot: Path to the chroot.
268
269 Returns:
270 Path to an image file that would be associated with chroot.
271 """
272 return chroot.rstrip('/') + '.img'
273
274
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600275def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
276 """Create a snapshot for the specified chroot VG/LV.
277
278 Args:
279 snapshot_name: The name of the new snapshot.
280 chroot_vg: The name of the VG containing the origin LV.
281 chroot_lv: The name of the origin LV.
282
283 Returns:
284 True if the snapshot was created, or False if a snapshot with the same
285 name already exists.
286
287 Raises:
288 SystemExit: The lvcreate command failed.
289 """
290 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
Manoj Guptab12f7302019-06-03 16:40:14 -0700291 logging.error(
292 'Cannot create snapshot %s: A volume with that name already '
293 'exists.', snapshot_name)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600294 return False
295
Manoj Guptab12f7302019-06-03 16:40:14 -0700296 cmd = [
297 'lvcreate', '-s', '--name', snapshot_name,
298 '%s/%s' % (chroot_vg, chroot_lv)
299 ]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600300 try:
301 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
302 chroot_lv, chroot_vg)
303 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
304 return True
305 except cros_build_lib.RunCommandError:
306 raise SystemExit('Running %r failed!' % cmd)
307
308
309def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
310 """Delete the named snapshot from the specified chroot VG.
311
312 If the requested snapshot is not found, nothing happens. The main chroot LV
313 and internal thinpool LV cannot be deleted with this function.
314
315 Args:
316 snapshot_name: The name of the snapshot to delete.
317 chroot_vg: The name of the VG containing the origin LV.
318 chroot_lv: The name of the origin LV.
319
320 Raises:
321 SystemExit: The lvremove command failed.
322 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600323 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
324 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Manoj Guptab12f7302019-06-03 16:40:14 -0700325 logging.error(
326 'Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
327 'if you want to remove the whole chroot.', snapshot_name)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600328 return
329
330 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
331 return
332
333 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
334 try:
335 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
336 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
337 except cros_build_lib.RunCommandError:
338 raise SystemExit('Running %r failed!' % cmd)
339
340
341def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
342 """Restore the chroot to an existing snapshot.
343
344 This is done by renaming the original |chroot_lv| LV to a temporary name,
345 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
346 now unused LV. If an error occurs, attempts to rename the original snapshot
347 back to |chroot_lv| to leave the chroot unchanged.
348
349 The chroot must be unmounted before calling this function, and will be left
350 unmounted after this function returns.
351
352 Args:
353 snapshot_name: The name of the snapshot to restore. This snapshot will no
354 longer be accessible at its original name after this function finishes.
355 chroot_vg: The VG containing the chroot LV and snapshot LV.
356 chroot_lv: The name of the original chroot LV.
357
358 Returns:
359 True if the chroot was restored to the requested snapshot, or False if
360 the snapshot wasn't found or isn't valid.
361
362 Raises:
363 SystemExit: Any of the LVM commands failed.
364 """
365 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600366 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
367 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600368 snapshot_name not in valid_snapshots):
369 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
370 snapshot_name, ', '.join(valid_snapshots))
371 return False
372
373 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
374 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
375 try:
376 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
377 except cros_build_lib.RunCommandError:
378 raise SystemExit('Running %r failed!' % cmd)
379
380 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
381 try:
382 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
383 except cros_build_lib.RunCommandError:
384 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
385 try:
386 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
387 except cros_build_lib.RunCommandError:
388 raise SystemExit('Failed to rename %s to chroot and failed to restore '
389 '%s back to chroot. Failed command: %r' %
390 (snapshot_name, backup_chroot_name, cmd))
Manoj Guptab12f7302019-06-03 16:40:14 -0700391 raise SystemExit(
392 'Failed to rename %s to chroot. Original chroot LV has '
393 'been restored. Failed command: %r' % (snapshot_name, cmd))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600394
395 # Some versions of LVM set snapshots to be skipped at auto-activate time.
396 # Other versions don't have this flag at all. We run lvchange to try
397 # disabling auto-skip and activating the volume, but ignore errors. Versions
398 # that don't have the flag should be auto-activated.
399 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
400 cmd = ['lvchange', '-kn', chroot_lv_path]
Manoj Guptab12f7302019-06-03 16:40:14 -0700401 cros_build_lib.RunCommand(
402 cmd, print_cmd=False, capture_output=True, error_code_ok=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600403
404 # Activate the LV in case the lvchange above was needed. Activating an LV
405 # that is already active shouldn't do anything, so this is safe to run even if
406 # the -kn wasn't needed.
407 cmd = ['lvchange', '-ay', chroot_lv_path]
408 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
409
410 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
411 try:
412 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
413 except cros_build_lib.RunCommandError:
414 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
415 (chroot_vg, backup_chroot_name, cmd))
416
417 return True
418
419
420def ListChrootSnapshots(chroot_vg, chroot_lv):
421 """Return all snapshots in |chroot_vg| regardless of origin volume.
422
423 Args:
424 chroot_vg: The name of the VG containing the chroot.
425 chroot_lv: The name of the chroot LV.
426
427 Returns:
428 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
429
430 Raises:
431 SystemExit: The lvs command failed.
432 """
433 if not chroot_vg or not chroot_lv:
434 return []
435
Manoj Guptab12f7302019-06-03 16:40:14 -0700436 cmd = [
437 'lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name', '--noheadings',
438 '--separator', '\t', chroot_vg
439 ]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600440 try:
Manoj Guptab12f7302019-06-03 16:40:14 -0700441 result = cros_build_lib.RunCommand(
442 cmd, print_cmd=False, redirect_stdout=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600443 except cros_build_lib.RunCommandError:
444 raise SystemExit('Running %r failed!' % cmd)
445
446 # Once the thin origin volume has been deleted, there's no way to tell a
447 # snapshot apart from any other volume. Since this VG is created and managed
448 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
449 # valid snapshots.
450 snapshots = []
451 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
452 for line in result.output.splitlines():
453 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
Manoj Guptab12f7302019-06-03 16:40:14 -0700454 if (lv_name == chroot_lv or lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon74645232018-05-04 17:40:42 -0600455 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600456 not snapshot_attrs.match(lv_attr)):
457 continue
458 snapshots.append(lv_name)
459 return snapshots
460
461
David James56e6c2c2012-10-24 23:54:41 -0700462def _SudoCommand():
463 """Get the 'sudo' command, along with all needed environment variables."""
464
David James5a73b4d2013-03-07 10:23:40 -0800465 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
466 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700467 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800468 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + 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
473 # Pass in the path to the depot_tools so that users can access them from
474 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400475 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500476
David James56e6c2c2012-10-24 23:54:41 -0700477 return cmd
478
479
Josh Triplett472a4182013-03-08 11:48:57 -0800480def _ReportMissing(missing):
481 """Report missing utilities, then exit.
482
483 Args:
484 missing: List of missing utilities, as returned by
485 osutils.FindMissingBinaries. If non-empty, will not return.
486 """
487
488 if missing:
489 raise SystemExit(
490 'The tool(s) %s were not found.\n'
491 'Please install the appropriate package in your host.\n'
492 'Example(ubuntu):\n'
Manoj Guptab12f7302019-06-03 16:40:14 -0700493 ' sudo apt-get install <packagename>' % ', '.join(missing))
Josh Triplett472a4182013-03-08 11:48:57 -0800494
495
496def _ProxySimSetup(options):
497 """Set up proxy simulator, and return only in the child environment.
498
499 TODO: Ideally, this should support multiple concurrent invocations of
500 cros_sdk --proxy-sim; currently, such invocations will conflict with each
501 other due to the veth device names and IP addresses. Either this code would
502 need to generate fresh, unused names for all of these before forking, or it
503 would need to support multiple concurrent cros_sdk invocations sharing one
504 proxy and allowing it to exit when unused (without counting on any local
505 service-management infrastructure on the host).
506 """
507
508 may_need_mpm = False
509 apache_bin = osutils.Which('apache2')
510 if apache_bin is None:
511 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
512 if apache_bin is None:
513 _ReportMissing(('apache2',))
514 else:
515 may_need_mpm = True
516
517 # Module names and .so names included for ease of grepping.
518 apache_modules = [('proxy_module', 'mod_proxy.so'),
519 ('proxy_connect_module', 'mod_proxy_connect.so'),
520 ('proxy_http_module', 'mod_proxy_http.so'),
521 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
522
523 # Find the apache module directory, and make sure it has the modules we need.
524 module_dirs = {}
525 for g in PROXY_APACHE_MODULE_GLOBS:
526 for mod, so in apache_modules:
527 for f in glob.glob(os.path.join(g, so)):
528 module_dirs.setdefault(os.path.dirname(f), []).append(so)
529 for apache_module_path, modules_found in module_dirs.iteritems():
530 if len(modules_found) == len(apache_modules):
531 break
532 else:
533 # Appease cros lint, which doesn't understand that this else block will not
534 # fall through to the subsequent code which relies on apache_module_path.
535 apache_module_path = None
536 raise SystemExit(
537 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500538 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800539
540 def check_add_module(name):
541 so = 'mod_%s.so' % name
542 if os.access(os.path.join(apache_module_path, so), os.F_OK):
543 mod = '%s_module' % name
544 apache_modules.append((mod, so))
545 return True
546 return False
547
548 check_add_module('authz_core')
549 if may_need_mpm:
550 for mpm in PROXY_APACHE_MPMS:
551 if check_add_module('mpm_%s' % mpm):
552 break
553
554 veth_host = '%s-host' % PROXY_VETH_PREFIX
555 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
556
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500557 # Set up locks to sync the net namespace setup. We need the child to create
558 # the net ns first, and then have the parent assign the guest end of the veth
559 # interface to the child's new network namespace & bring up the proxy. Only
560 # then can the child move forward and rely on the network being up.
561 ns_create_lock = locking.PipeLock()
562 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800563
564 pid = os.fork()
565 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500566 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800567 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500568
569 # Signal the parent the ns is ready to be configured.
570 ns_create_lock.Post()
571 del ns_create_lock
572
573 # Wait for the parent to finish setting up the ns/proxy.
574 ns_setup_lock.Wait()
575 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800576
577 # Set up child side of the network.
578 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500579 ('ip', 'link', 'set', 'up', 'lo'),
Manoj Guptab12f7302019-06-03 16:40:14 -0700580 ('ip', 'address', 'add', '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500581 'dev', veth_guest),
582 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800583 )
584 try:
585 for cmd in commands:
586 cros_build_lib.RunCommand(cmd, print_cmd=False)
587 except cros_build_lib.RunCommandError:
588 raise SystemExit('Running %r failed!' % (cmd,))
589
590 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
591 for proto in ('http', 'https', 'ftp'):
592 os.environ[proto + '_proxy'] = proxy_url
593 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
594 os.environ.pop(v, None)
595 return
596
Josh Triplett472a4182013-03-08 11:48:57 -0800597 # Set up parent side of the network.
598 uid = int(os.environ.get('SUDO_UID', '0'))
599 gid = int(os.environ.get('SUDO_GID', '0'))
600 if uid == 0 or gid == 0:
601 for username in PROXY_APACHE_FALLBACK_USERS:
602 try:
603 pwnam = pwd.getpwnam(username)
604 uid, gid = pwnam.pw_uid, pwnam.pw_gid
605 break
606 except KeyError:
607 continue
608 if uid == 0 or gid == 0:
609 raise SystemExit('Could not find a non-root user to run Apache as')
610
611 chroot_parent, chroot_base = os.path.split(options.chroot)
612 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
613 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
614
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500615 # Wait for the child to create the net ns.
616 ns_create_lock.Wait()
617 del ns_create_lock
618
Josh Triplett472a4182013-03-08 11:48:57 -0800619 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500620 'User #%u' % uid,
621 'Group #%u' % gid,
622 'PidFile %s' % pid_file,
623 'ErrorLog %s' % log_file,
624 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
625 'ServerName %s' % PROXY_HOST_IP,
626 'ProxyRequests On',
627 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800628 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500629 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
630 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800631 ]
632 commands = (
Manoj Guptab12f7302019-06-03 16:40:14 -0700633 ('ip', 'link', 'add', 'name', veth_host, 'type', 'veth', 'peer', 'name',
634 veth_guest),
635 ('ip', 'address', 'add', '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK), 'dev',
636 veth_host),
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500637 ('ip', 'link', 'set', veth_host, 'up'),
638 ([apache_bin, '-f', '/dev/null'] +
639 [arg for d in apache_directives for arg in ('-C', d)]),
640 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800641 )
Manoj Guptab12f7302019-06-03 16:40:14 -0700642 cmd = None # Make cros lint happy.
Josh Triplett472a4182013-03-08 11:48:57 -0800643 try:
644 for cmd in commands:
645 cros_build_lib.RunCommand(cmd, print_cmd=False)
646 except cros_build_lib.RunCommandError:
647 # Clean up existing interfaces, if any.
648 cmd_cleanup = ('ip', 'link', 'del', veth_host)
649 try:
650 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
651 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700652 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800653 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500654
655 # Signal the child that the net ns/proxy is fully configured now.
656 ns_setup_lock.Post()
657 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800658
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400659 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800660
661
Mike Frysingera78a56e2012-11-20 06:02:30 -0500662def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700663 """Re-execute cros_sdk as root.
664
665 Also unshare the mount namespace so as to ensure that processes outside
666 the chroot can't mess with our mounts.
667 """
668 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500669 cmd = _SudoCommand() + ['--'] + argv
670 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500671 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400672 # We must set up the cgroups mounts before we enter our own namespace.
673 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800674 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700675
676
Mike Frysinger34db8692013-11-11 14:54:08 -0500677def _CreateParser(sdk_latest_version, bootstrap_latest_version):
678 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400679 usage = ('usage: %(prog)s [options] '
680 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
Manoj Guptab12f7302019-06-03 16:40:14 -0700681 parser = commandline.ArgumentParser(
682 usage=usage, description=__doc__, caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700683
Mike Frysinger34db8692013-11-11 14:54:08 -0500684 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500685 default_chroot = os.path.join(constants.SOURCE_ROOT,
686 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400687 parser.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700688 '--chroot',
689 dest='chroot',
690 default=default_chroot,
691 type='path',
Brian Harring218e13c2012-10-10 16:21:26 -0700692 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Manoj Guptab12f7302019-06-03 16:40:14 -0700693 parser.add_argument(
694 '--nouse-image',
695 dest='use_image',
696 action='store_false',
697 default=True,
698 help='Do not mount the chroot on a loopback image; '
699 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800700
Manoj Guptab12f7302019-06-03 16:40:14 -0700701 parser.add_argument(
Alex Klein5e4b1bc2019-07-02 12:27:06 -0600702 '--chrome-root',
Manoj Guptab12f7302019-06-03 16:40:14 -0700703 '--chrome_root',
704 type='path',
705 help='Mount this chrome root into the SDK chroot')
706 parser.add_argument(
707 '--chrome_root_mount',
708 type='path',
709 help='Mount chrome into this path inside SDK chroot')
710 parser.add_argument(
711 '--nousepkg',
712 action='store_true',
713 default=False,
714 help='Do not use binary packages when creating a chroot.')
715 parser.add_argument(
716 '-u',
717 '--url',
718 dest='sdk_url',
719 help='Use sdk tarball located at this url. Use file:// '
720 'for local files.')
721 parser.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700722 '--sdk-version',
723 help=('Use this sdk version. For prebuilt, current is %r'
724 ', for bootstrapping it is %r.' % (sdk_latest_version,
725 bootstrap_latest_version)))
726 parser.add_argument(
727 '--goma_dir',
728 type='path',
729 help='Goma installed directory to mount into the chroot.')
730 parser.add_argument(
731 '--goma_client_json',
732 type='path',
733 help='Service account json file to use goma on bot. '
734 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800735
736 # Use type=str instead of type='path' to prevent the given path from being
737 # transfered to absolute path automatically.
Manoj Guptab12f7302019-06-03 16:40:14 -0700738 parser.add_argument(
739 '--working-dir',
740 type=str,
741 help='Run the command in specific working directory in '
742 'chroot. If the given directory is a relative '
743 'path, this program will transfer the path to '
744 'the corresponding one inside chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800745
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400746 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500747
748 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400749 group = parser.add_argument_group('Commands')
750 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700751 '--enter',
752 action='store_true',
753 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500754 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400755 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700756 '--create',
757 action='store_true',
758 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500759 help='Create the chroot only if it does not already exist. '
760 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400761 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700762 '--bootstrap',
763 action='store_true',
764 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500765 help='Build everything from scratch, including the sdk. '
766 'Use this only if you need to validate a change '
767 'that affects SDK creation itself (toolchain and '
768 'build are typically the only folk who need this). '
769 'Note this will quite heavily slow down the build. '
770 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400771 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700772 '-r',
773 '--replace',
774 action='store_true',
775 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500776 help='Replace an existing SDK chroot. Basically an alias '
777 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400778 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700779 '--delete',
780 action='store_true',
781 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500782 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400783 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700784 '--unmount',
785 action='store_true',
786 default=False,
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600787 help='Unmount and clean up devices associated with the '
788 'SDK chroot if it exists. This does not delete the '
789 'backing image file, so the same chroot can be later '
790 're-mounted for reuse. To fully delete the chroot, use '
791 '--delete. This is primarily useful for working on '
792 'cros_sdk or the chroot setup; you should not need it '
793 'under normal circumstances.')
794 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700795 '--download',
796 action='store_true',
797 default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500798 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600799 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700800 '--snapshot-create',
801 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600802 help='Create a snapshot of the chroot. Requires that the chroot was '
Manoj Guptab12f7302019-06-03 16:40:14 -0700803 'created without the --nouse-image option.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600804 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700805 '--snapshot-restore',
806 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600807 help='Restore the chroot to a previously created snapshot.')
808 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700809 '--snapshot-delete',
810 metavar='SNAPSHOT_NAME',
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600811 help='Delete a previously created snapshot. Deleting a snapshot that '
Manoj Guptab12f7302019-06-03 16:40:14 -0700812 'does not exist is not an error.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600813 group.add_argument(
Manoj Guptab12f7302019-06-03 16:40:14 -0700814 '--snapshot-list',
815 action='store_true',
816 default=False,
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600817 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500818 commands = group
819
Mike Frysinger80dfce92014-04-21 10:58:53 -0400820 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400821 group = parser.add_argument_group('Namespaces')
Manoj Guptab12f7302019-06-03 16:40:14 -0700822 group.add_argument(
823 '--proxy-sim',
824 action='store_true',
825 default=False,
826 help='Simulate a restrictive network requiring an outbound'
827 ' proxy.')
828 group.add_argument(
829 '--no-ns-pid',
830 dest='ns_pid',
831 default=True,
832 action='store_false',
833 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400834
Mike Frysinger34db8692013-11-11 14:54:08 -0500835 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400836 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500837 'Internal Chromium OS Build Team Options',
838 'Caution: these are for meant for the Chromium OS build team only')
Manoj Guptab12f7302019-06-03 16:40:14 -0700839 group.add_argument(
840 '--buildbot-log-version',
841 default=False,
842 action='store_true',
843 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500844
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400845 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500846
847
848def main(argv):
849 conf = cros_build_lib.LoadKeyValueFile(
850 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
851 ignore_missing=True)
852 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
Manoj Gupta01927c12019-05-13 17:33:14 -0700853 bootstrap_frozen_version = conf.get('BOOTSTRAP_FROZEN_VERSION', '<unknown>')
Manoj Gupta55a63092019-06-13 11:47:13 -0700854
855 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
856 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
857 bootstrap_latest_version = (
858 sdk_latest_version
859 if bootstrap_frozen_version == '<unknown>' else bootstrap_frozen_version)
Mike Frysinger34db8692013-11-11 14:54:08 -0500860 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400861 options = parser.parse_args(argv)
862 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800863
864 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500865 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800866
Brian Harring1790ac42012-09-23 08:53:33 -0700867 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700868 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600869 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700870 "cros_sdk is currently only supported on x86_64; you're running"
871 " %s. Please find a x86_64 machine." % (host,))
872
Josh Triplett472a4182013-03-08 11:48:57 -0800873 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
874 if options.proxy_sim:
875 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600876 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800877
Benjamin Gordon040a1162017-06-29 13:44:47 -0600878 if (sdk_latest_version == '<unknown>' or
879 bootstrap_latest_version == '<unknown>'):
880 cros_build_lib.Die(
881 'No SDK version was found. '
882 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
883 'Please change to a directory inside your Chromium OS source tree\n'
884 'and retry. If you need to setup a Chromium OS source tree, see\n'
Mike Frysingerdcad4e02018-08-03 16:20:02 -0400885 ' https://dev.chromium.org/chromium-os/developer-guide')
Benjamin Gordon040a1162017-06-29 13:44:47 -0600886
Manoj Guptab12f7302019-06-03 16:40:14 -0700887 any_snapshot_operation = (
888 options.snapshot_create or options.snapshot_restore or
889 options.snapshot_delete or options.snapshot_list)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600890 if any_snapshot_operation and not options.use_image:
891 cros_build_lib.Die('Snapshot operations are not compatible with '
892 '--nouse-image.')
893
Manoj Guptab12f7302019-06-03 16:40:14 -0700894 if (options.snapshot_delete and
895 options.snapshot_delete == options.snapshot_restore):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600896 parser.error('Cannot --snapshot_delete the same snapshot you are '
897 'restoring with --snapshot_restore.')
898
David James471532c2013-01-21 10:23:31 -0800899 _ReExecuteIfNeeded([sys.argv[0]] + argv)
900
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600901 lock_path = os.path.dirname(options.chroot)
902 lock_path = os.path.join(
903 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
904
Brian Harring218e13c2012-10-10 16:21:26 -0700905 # Expand out the aliases...
906 if options.replace:
907 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800908
Brian Harring218e13c2012-10-10 16:21:26 -0700909 if options.bootstrap:
910 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800911
Brian Harring218e13c2012-10-10 16:21:26 -0700912 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400913 # pylint: disable=protected-access
914 # This _group_actions access sucks, but upstream decided to not include an
915 # alternative to optparse's option_list, and this is what they recommend.
Manoj Guptab12f7302019-06-03 16:40:14 -0700916 options.enter |= not any(
917 getattr(options, x.dest) for x in commands._group_actions)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400918 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700919 options.enter |= bool(chroot_command)
920
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600921 if (options.delete and not options.create and
922 (options.enter or any_snapshot_operation)):
923 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700924 "was specified makes no sense.")
925
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600926 if (options.unmount and
927 (options.create or options.enter or any_snapshot_operation)):
928 parser.error('--unmount cannot be specified with other chroot actions.')
929
Yong Hong84ba9172018-02-07 01:37:42 +0800930 if options.working_dir is not None and not os.path.isabs(options.working_dir):
931 options.working_dir = path_util.ToChrootPath(options.working_dir)
932
Benjamin Gordon35194f12017-07-19 10:26:22 -0600933 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600934 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600935 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600936 not options.unmount and not missing_image_tools and
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600937 os.path.exists(_ImageFileForChroot(options.chroot))):
938 # Try to re-mount an existing image in case the user has rebooted.
939 with cgroups.SimpleContainChildren('cros_sdk'):
940 with locking.FileLock(lock_path, 'chroot lock') as lock:
941 logging.debug('Checking if existing chroot image can be mounted.')
942 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600943 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600944 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600945 if chroot_exists:
946 logging.notice('Mounted existing image %s on chroot',
947 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700948
949 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600950 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700951 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800952
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600953 # Make sure we will download if we plan to create.
954 options.download |= options.create
955
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600956 # Anything that needs to manipulate the main chroot mount or communicate with
957 # LVM needs to be done here before we enter the new namespaces.
958
959 # If deleting, do it regardless of the use_image flag so that a
960 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600961 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
962 # favor of this block.
963 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600964 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600965 with cgroups.SimpleContainChildren('cros_sdk'):
966 with locking.FileLock(lock_path, 'chroot lock') as lock:
967 lock.write_lock()
968 if missing_image_tools:
969 logging.notice('Unmounting chroot.')
970 osutils.UmountTree(options.chroot)
971 else:
972 logging.notice('Deleting chroot.')
Don Garrett36650112018-06-28 15:54:34 -0700973 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600974 chroot_deleted = True
975
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600976 # If cleanup was requested, we have to do it while we're still in the original
977 # namespace. Since cleaning up the mount will interfere with any other
978 # commands, we exit here. The check above should have made sure that no other
979 # action was requested, anyway.
980 if options.unmount:
981 with locking.FileLock(lock_path, 'chroot lock') as lock:
982 lock.write_lock()
983 CleanupChroot(options.chroot)
984 sys.exit(0)
985
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600986 # Make sure the main chroot mount is visible. Contents will be filled in
987 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600988 if options.create and options.use_image:
989 if missing_image_tools:
Manoj Guptab12f7302019-06-03 16:40:14 -0700990 raise SystemExit('''The tool(s) %s were not found.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600991Please make sure the lvm2 and thin-provisioning-tools packages
992are installed on your host.
993Example(ubuntu):
994 sudo apt-get install lvm2 thin-provisioning-tools
995
996If you want to run without lvm2, pass --nouse-image (chroot
997snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600998
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600999 logging.debug('Making sure chroot image is mounted.')
1000 with cgroups.SimpleContainChildren('cros_sdk'):
1001 with locking.FileLock(lock_path, 'chroot lock') as lock:
1002 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -06001003 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001004 cros_build_lib.Die('Unable to mount %s on chroot',
1005 _ImageFileForChroot(options.chroot))
1006 logging.notice('Mounted %s on chroot',
1007 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001008
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001009 # Snapshot operations will always need the VG/LV, but other actions won't.
1010 if any_snapshot_operation:
1011 with cgroups.SimpleContainChildren('cros_sdk'):
1012 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -06001013 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001014 options.chroot)
1015 if not chroot_vg or not chroot_lv:
1016 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
1017 options.chroot)
1018
1019 # Delete snapshot before creating a new one. This allows the user to
1020 # throw out old state, create a new snapshot, and enter the chroot in a
1021 # single call to cros_sdk. Since restore involves deleting, also do it
1022 # before creating.
1023 if options.snapshot_restore:
1024 lock.write_lock()
1025 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1026 if options.snapshot_restore not in valid_snapshots:
Manoj Guptab12f7302019-06-03 16:40:14 -07001027 cros_build_lib.Die(
1028 '%s is not a valid snapshot to restore to. '
1029 'Valid snapshots: %s', options.snapshot_restore,
1030 ', '.join(valid_snapshots))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001031 osutils.UmountTree(options.chroot)
1032 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1033 chroot_lv):
1034 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -06001035 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001036 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1037
1038 # Use a read lock for snapshot delete and create even though they modify
1039 # the filesystem, because they don't modify the mounted chroot itself.
1040 # The underlying LVM commands take their own locks, so conflicting
1041 # concurrent operations here may crash cros_sdk, but won't corrupt the
1042 # chroot image. This tradeoff seems worth it to allow snapshot
1043 # operations on chroots that have a process inside.
1044 if options.snapshot_delete:
1045 lock.read_lock()
1046 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1047
1048 if options.snapshot_create:
1049 lock.read_lock()
1050 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1051 chroot_lv):
1052 cros_build_lib.Die('Unable to create snapshot.')
1053
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001054 img_path = _ImageFileForChroot(options.chroot)
1055 if (options.use_image and os.path.exists(options.chroot) and
1056 os.path.exists(img_path)):
1057 img_stat = os.stat(img_path)
1058 img_used_bytes = img_stat.st_blocks * 512
1059
1060 mount_stat = os.statvfs(options.chroot)
Manoj Guptab12f7302019-06-03 16:40:14 -07001061 mount_used_bytes = mount_stat.f_frsize * (
1062 mount_stat.f_blocks - mount_stat.f_bfree)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001063
1064 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1065 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1066 logging.notice('%s is using %s GiB more than needed. Running '
1067 'fstrim.', img_path, extra_gbs)
1068 cmd = ['fstrim', options.chroot]
1069 try:
1070 cros_build_lib.RunCommand(cmd, print_cmd=False)
1071 except cros_build_lib.RunCommandError as e:
Manoj Guptab12f7302019-06-03 16:40:14 -07001072 logging.warning(
1073 'Running fstrim failed. Consider running fstrim on '
1074 'your chroot manually.\nError: %s', e)
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001075
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001076 # Enter a new set of namespaces. Everything after here cannot directly affect
1077 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001078 namespaces.SimpleUnshare()
1079 if options.ns_pid:
1080 first_pid = namespaces.CreatePidNs()
1081 else:
1082 first_pid = None
1083
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001084 if options.snapshot_list:
1085 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1086 print(snap)
1087 sys.exit(0)
1088
Brian Harringb938c782012-02-29 15:14:38 -08001089 if not options.sdk_version:
Manoj Guptab12f7302019-06-03 16:40:14 -07001090 sdk_version = (
1091 bootstrap_latest_version if options.bootstrap else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001092 else:
1093 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001094 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001095 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001096
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001097 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001098 if options.download:
1099 if options.sdk_url:
1100 urls = [options.sdk_url]
Yong Hong4e29b622018-02-05 14:31:10 +08001101 else:
1102 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001103
Mike Frysinger80dfce92014-04-21 10:58:53 -04001104 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001105 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001106 if options.proxy_sim:
1107 _ProxySimSetup(options)
1108
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001109 if (options.delete and not chroot_deleted and
1110 (os.path.exists(options.chroot) or
1111 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001112 lock.write_lock()
1113 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001114
David James56e6c2c2012-10-24 23:54:41 -07001115 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1116 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001117 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001118
David James56e6c2c2012-10-24 23:54:41 -07001119 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001120 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001121 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001122 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001123 continue
1124 lock.write_lock(
1125 "Upgrade to %r needed but chroot is locked; please exit "
1126 "all instances so this upgrade can finish." % src)
1127 if not os.path.exists(src):
1128 # Note that while waiting for the write lock, src may've vanished;
1129 # it's a rare race during the upgrade process that's a byproduct
1130 # of us avoiding taking a write lock to do the src check. If we
1131 # took a write lock for that check, it would effectively limit
1132 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001133 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001134 elif not os.path.exists(target):
1135 # Upgrade occurred, but a reversion, or something whacky
1136 # occurred writing to the old location. Wipe and continue.
1137 os.rename(src, target)
1138 else:
1139 # Upgrade occurred once already, but either a reversion or
1140 # some before/after separate cros_sdk usage is at play.
1141 # Wipe and continue.
1142 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001143
David James56e6c2c2012-10-24 23:54:41 -07001144 if options.download:
1145 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001146 sdk_tarball = FetchRemoteTarballs(
1147 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001148
David James56e6c2c2012-10-24 23:54:41 -07001149 if options.create:
1150 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001151 # Recheck if the chroot is set up here before creating to make sure we
1152 # account for whatever the various delete/unmount/remount steps above
1153 # have done.
1154 if cros_sdk_lib.IsChrootReady(options.chroot):
1155 logging.debug('Chroot already exists. Skipping creation.')
1156 else:
Manoj Guptab12f7302019-06-03 16:40:14 -07001157 CreateChroot(
1158 options.chroot,
1159 sdk_tarball,
1160 options.cache_dir,
1161 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001162
David James56e6c2c2012-10-24 23:54:41 -07001163 if options.enter:
1164 lock.read_lock()
1165 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Mike Frysinger0b2d9ee2019-02-28 17:05:47 -05001166 options.chrome_root_mount, options.goma_dir,
1167 options.goma_client_json, options.working_dir,
1168 chroot_command)