blob: b378a2e4eecd26a7511b73eab2599d562b7f0b4a [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.
4
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 Frysinger383367e2014-09-16 15:06:17 -040014from __future__ import print_function
15
Mike Frysinger2f95cfc2015-06-04 04:00:26 -040016import argparse
Josh Triplett472a4182013-03-08 11:48:57 -080017import glob
Brian Harringb938c782012-02-29 15:14:38 -080018import os
Josh Triplett472a4182013-03-08 11:48:57 -080019import pwd
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060020import random
Brian Norrisd37e2f72016-08-22 16:09:24 -070021import re
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -070022import resource
David James56e6c2c2012-10-24 23:54:41 -070023import sys
Brian Harringb938c782012-02-29 15:14:38 -080024import urlparse
25
Aviv Keshetb7519e12016-10-04 00:50:00 -070026from chromite.lib import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080027from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070028from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080029from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070030from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080031from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080032from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070033from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040034from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080035from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050036from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080037
38cros_build_lib.STRICT_SUDO = True
39
40
Zdenek Behanaa52cea2012-05-30 01:31:11 +020041COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020042
Brian Harringb938c782012-02-29 15:14:38 -080043# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050044MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
45 'src/scripts/sdk_lib/make_chroot.sh')]
46ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
47 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080048
Josh Triplett472a4182013-03-08 11:48:57 -080049# Proxy simulator configuration.
50PROXY_HOST_IP = '192.168.240.1'
51PROXY_PORT = 8080
52PROXY_GUEST_IP = '192.168.240.2'
53PROXY_NETMASK = 30
54PROXY_VETH_PREFIX = 'veth'
55PROXY_CONNECT_PORTS = (80, 443, 9418)
56PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
57PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
58PROXY_APACHE_FALLBACK_PATH = ':'.join(
59 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
60)
61PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
62
Josh Triplett9a495f62013-03-15 18:06:55 -070063# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080064NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080065
Josh Triplett472a4182013-03-08 11:48:57 -080066# Tools needed for --proxy-sim only.
67PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080068
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060069# Tools needed when use_image is true (the default).
70IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060071 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060072
Mike Frysingercc838832014-05-24 13:10:30 -040073
Brian Harring1790ac42012-09-23 08:53:33 -070074def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080075 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070076 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050077 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
78 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070079 for compressor in COMPRESSION_PREFERENCE]
80
81
82def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050083 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070084 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080085
86
Gilad Arnold6a8f0452015-06-04 11:25:18 -070087def GetToolchainsOverlayUrls(version, toolchains):
88 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070089
90 Args:
91 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
92 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -070093 overlays are stored (.../2015/05/ in this case).
94 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070095
96 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -070097 List of alternative download URLs for an SDK overlay tarball that contains
98 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070099 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700100 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700101 suburl_template = os.path.join(
102 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700103 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
104 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700105 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
106 for ext in COMPRESSION_PREFERENCE]
107
108
109def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500110 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111
112 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500113 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200114 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700115 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700116 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200117
118 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700119 Full path to the downloaded file, or None if |allow_none| and no URL worked.
120
121 Raises:
122 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200123 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200124
Brian Harring1790ac42012-09-23 08:53:33 -0700125 # Note we track content length ourselves since certain versions of curl
126 # fail if asked to resume a complete file.
127 # pylint: disable=C0301,W0631
128 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700129 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700130 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200131 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700132 # http://www.logilab.org/ticket/8766
133 # pylint: disable=E1101
134 parsed = urlparse.urlparse(url)
135 tarball_name = os.path.basename(parsed.path)
136 if parsed.scheme in ('', 'file'):
137 if os.path.exists(parsed.path):
138 return parsed.path
139 continue
140 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700141 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800142 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900143 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
144 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700145 successful = False
146 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700147 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700148 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700149 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700150 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500151 elif header.lower().startswith('content-length:'):
152 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700153 if successful:
154 break
155 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200156 break
157 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700158 if allow_none:
159 return None
160 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200161
Brian Harringae0a5322012-09-15 01:46:51 -0700162 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700163 current_size = 0
164 if os.path.exists(tarball_dest):
165 current_size = os.path.getsize(tarball_dest)
166 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700167 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700168 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100169
Brian Harring1790ac42012-09-23 08:53:33 -0700170 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800171 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900172 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
173 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800174
Brian Harring1790ac42012-09-23 08:53:33 -0700175 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700176 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
177 # because we might have partial overlap between known prefixes.
178 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
179 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
180 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700181 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700182 if (filename == tarball_name or
183 any([(filename.startswith(p) and
184 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
185 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700186 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700187 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700188 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200189
Brian Harringb938c782012-02-29 15:14:38 -0800190 return tarball_dest
191
192
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700193def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600194 cache_dir, nousepkg=False):
195 """Creates a new chroot from a given SDK.
196
197 Args:
198 chroot_path: Path where the new chroot will be created.
199 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
200 toolchains_overlay_tarball: Optional path to a second tarball that will be
201 unpacked into the chroot on top of the SDK tarball.
202 cache_dir: Path to a directory that will be used for caching portage files,
203 etc.
204 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
205 chroot.
206 """
Brian Harringb938c782012-02-29 15:14:38 -0800207
Brian Harring1790ac42012-09-23 08:53:33 -0700208 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700209 '--chroot', chroot_path,
210 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700211
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700212 if toolchains_overlay_tarball:
213 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700214
Mike Frysinger2de7f042012-07-10 04:45:03 -0400215 if nousepkg:
216 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800217
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700218 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800219 try:
220 cros_build_lib.RunCommand(cmd, print_cmd=False)
221 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700222 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800223
224
225def DeleteChroot(chroot_path):
226 """Deletes an existing chroot"""
227 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
228 '--delete']
229 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700230 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800231 cros_build_lib.RunCommand(cmd, print_cmd=False)
232 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700233 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800234
235
Brian Harringae0a5322012-09-15 01:46:51 -0700236def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900237 workspace, goma_dir, goma_client_json, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800238 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400239 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
240 # The os.ST_NOSUID constant wasn't added until python-3.2.
241 if st.f_flag & 0x2:
242 cros_build_lib.Die('chroot cannot be in a nosuid mount')
243
Brian Harringae0a5322012-09-15 01:46:51 -0700244 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800245 if chrome_root:
246 cmd.extend(['--chrome_root', chrome_root])
247 if chrome_root_mount:
248 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700249 if workspace:
250 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900251 if goma_dir:
252 cmd.extend(['--goma_dir', goma_dir])
253 if goma_client_json:
254 cmd.extend(['--goma_client_json', goma_client_json])
Don Garrett230d1b22015-03-09 16:21:19 -0700255
Brian Harringb938c782012-02-29 15:14:38 -0800256 if len(additional_args) > 0:
257 cmd.append('--')
258 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700259
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700260 # ThinLTO opens lots of files at the same time.
261 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700262 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
263 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700264 # If we were in interactive mode, ignore the exit code; it'll be whatever
265 # they last ran w/in the chroot and won't matter to us one way or another.
266 # Note this does allow chroot entrance to fail and be ignored during
267 # interactive; this is however a rare case and the user will immediately
268 # see it (nor will they be checking the exit code manually).
269 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700270 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800271
272
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600273def _ImageFileForChroot(chroot):
274 """Find the image file that should be associated with |chroot|.
275
276 This function does not check if the image exists; it simply returns the
277 filename that would be used.
278
279 Args:
280 chroot: Path to the chroot.
281
282 Returns:
283 Path to an image file that would be associated with chroot.
284 """
285 return chroot.rstrip('/') + '.img'
286
287
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600288def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
289 """Create a snapshot for the specified chroot VG/LV.
290
291 Args:
292 snapshot_name: The name of the new snapshot.
293 chroot_vg: The name of the VG containing the origin LV.
294 chroot_lv: The name of the origin LV.
295
296 Returns:
297 True if the snapshot was created, or False if a snapshot with the same
298 name already exists.
299
300 Raises:
301 SystemExit: The lvcreate command failed.
302 """
303 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
304 logging.error('Cannot create snapshot %s: A volume with that name already '
305 'exists.', snapshot_name)
306 return False
307
308 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
309 chroot_vg, chroot_lv)]
310 try:
311 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
312 chroot_lv, chroot_vg)
313 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
314 return True
315 except cros_build_lib.RunCommandError:
316 raise SystemExit('Running %r failed!' % cmd)
317
318
319def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
320 """Delete the named snapshot from the specified chroot VG.
321
322 If the requested snapshot is not found, nothing happens. The main chroot LV
323 and internal thinpool LV cannot be deleted with this function.
324
325 Args:
326 snapshot_name: The name of the snapshot to delete.
327 chroot_vg: The name of the VG containing the origin LV.
328 chroot_lv: The name of the origin LV.
329
330 Raises:
331 SystemExit: The lvremove command failed.
332 """
333 if snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
334 cros_build_lib.CHROOT_THINPOOL_NAME):
335 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
336 'if you want to remove the whole chroot.', snapshot_name)
337 return
338
339 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
340 return
341
342 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
343 try:
344 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
345 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
346 except cros_build_lib.RunCommandError:
347 raise SystemExit('Running %r failed!' % cmd)
348
349
350def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
351 """Restore the chroot to an existing snapshot.
352
353 This is done by renaming the original |chroot_lv| LV to a temporary name,
354 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
355 now unused LV. If an error occurs, attempts to rename the original snapshot
356 back to |chroot_lv| to leave the chroot unchanged.
357
358 The chroot must be unmounted before calling this function, and will be left
359 unmounted after this function returns.
360
361 Args:
362 snapshot_name: The name of the snapshot to restore. This snapshot will no
363 longer be accessible at its original name after this function finishes.
364 chroot_vg: The VG containing the chroot LV and snapshot LV.
365 chroot_lv: The name of the original chroot LV.
366
367 Returns:
368 True if the chroot was restored to the requested snapshot, or False if
369 the snapshot wasn't found or isn't valid.
370
371 Raises:
372 SystemExit: Any of the LVM commands failed.
373 """
374 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
375 if (snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
376 cros_build_lib.CHROOT_THINPOOL_NAME) or
377 snapshot_name not in valid_snapshots):
378 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
379 snapshot_name, ', '.join(valid_snapshots))
380 return False
381
382 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
383 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
384 try:
385 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
386 except cros_build_lib.RunCommandError:
387 raise SystemExit('Running %r failed!' % cmd)
388
389 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
390 try:
391 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
392 except cros_build_lib.RunCommandError:
393 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
394 try:
395 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
396 except cros_build_lib.RunCommandError:
397 raise SystemExit('Failed to rename %s to chroot and failed to restore '
398 '%s back to chroot. Failed command: %r' %
399 (snapshot_name, backup_chroot_name, cmd))
400 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
401 'been restored. Failed command: %r' %
402 (snapshot_name, cmd))
403
404 # Some versions of LVM set snapshots to be skipped at auto-activate time.
405 # Other versions don't have this flag at all. We run lvchange to try
406 # disabling auto-skip and activating the volume, but ignore errors. Versions
407 # that don't have the flag should be auto-activated.
408 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
409 cmd = ['lvchange', '-kn', chroot_lv_path]
410 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
411 error_code_ok=True)
412
413 # Activate the LV in case the lvchange above was needed. Activating an LV
414 # that is already active shouldn't do anything, so this is safe to run even if
415 # the -kn wasn't needed.
416 cmd = ['lvchange', '-ay', chroot_lv_path]
417 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
418
419 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
420 try:
421 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
422 except cros_build_lib.RunCommandError:
423 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
424 (chroot_vg, backup_chroot_name, cmd))
425
426 return True
427
428
429def ListChrootSnapshots(chroot_vg, chroot_lv):
430 """Return all snapshots in |chroot_vg| regardless of origin volume.
431
432 Args:
433 chroot_vg: The name of the VG containing the chroot.
434 chroot_lv: The name of the chroot LV.
435
436 Returns:
437 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
438
439 Raises:
440 SystemExit: The lvs command failed.
441 """
442 if not chroot_vg or not chroot_lv:
443 return []
444
445 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
446 '--noheadings', '--separator', '\t', chroot_vg]
447 try:
448 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
449 redirect_stdout=True)
450 except cros_build_lib.RunCommandError:
451 raise SystemExit('Running %r failed!' % cmd)
452
453 # Once the thin origin volume has been deleted, there's no way to tell a
454 # snapshot apart from any other volume. Since this VG is created and managed
455 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
456 # valid snapshots.
457 snapshots = []
458 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
459 for line in result.output.splitlines():
460 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
461 if (lv_name == chroot_lv or
462 lv_name == cros_build_lib.CHROOT_THINPOOL_NAME or
463 pool_lv != cros_build_lib.CHROOT_THINPOOL_NAME or
464 not snapshot_attrs.match(lv_attr)):
465 continue
466 snapshots.append(lv_name)
467 return snapshots
468
469
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600470def _FindSubmounts(*args):
471 """Find all mounts matching each of the paths in |args| and any submounts.
472
473 Returns:
474 A list of all matching mounts in the order found in /proc/mounts.
475 """
476 mounts = []
477 paths = [p.rstrip('/') for p in args]
478 for mtab in osutils.IterateMountPoints():
479 for path in paths:
480 if mtab.destination == path or mtab.destination.startswith(path + '/'):
481 mounts.append(mtab.destination)
482 break
483
484 return mounts
485
486
David James56e6c2c2012-10-24 23:54:41 -0700487def _SudoCommand():
488 """Get the 'sudo' command, along with all needed environment variables."""
489
David James5a73b4d2013-03-07 10:23:40 -0800490 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
491 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700492 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800493 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700494 value = os.environ.get(key)
495 if value is not None:
496 cmd += ['%s=%s' % (key, value)]
497
498 # Pass in the path to the depot_tools so that users can access them from
499 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400500 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500501
David James56e6c2c2012-10-24 23:54:41 -0700502 return cmd
503
504
Josh Triplett472a4182013-03-08 11:48:57 -0800505def _ReportMissing(missing):
506 """Report missing utilities, then exit.
507
508 Args:
509 missing: List of missing utilities, as returned by
510 osutils.FindMissingBinaries. If non-empty, will not return.
511 """
512
513 if missing:
514 raise SystemExit(
515 'The tool(s) %s were not found.\n'
516 'Please install the appropriate package in your host.\n'
517 'Example(ubuntu):\n'
518 ' sudo apt-get install <packagename>'
519 % ', '.join(missing))
520
521
522def _ProxySimSetup(options):
523 """Set up proxy simulator, and return only in the child environment.
524
525 TODO: Ideally, this should support multiple concurrent invocations of
526 cros_sdk --proxy-sim; currently, such invocations will conflict with each
527 other due to the veth device names and IP addresses. Either this code would
528 need to generate fresh, unused names for all of these before forking, or it
529 would need to support multiple concurrent cros_sdk invocations sharing one
530 proxy and allowing it to exit when unused (without counting on any local
531 service-management infrastructure on the host).
532 """
533
534 may_need_mpm = False
535 apache_bin = osutils.Which('apache2')
536 if apache_bin is None:
537 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
538 if apache_bin is None:
539 _ReportMissing(('apache2',))
540 else:
541 may_need_mpm = True
542
543 # Module names and .so names included for ease of grepping.
544 apache_modules = [('proxy_module', 'mod_proxy.so'),
545 ('proxy_connect_module', 'mod_proxy_connect.so'),
546 ('proxy_http_module', 'mod_proxy_http.so'),
547 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
548
549 # Find the apache module directory, and make sure it has the modules we need.
550 module_dirs = {}
551 for g in PROXY_APACHE_MODULE_GLOBS:
552 for mod, so in apache_modules:
553 for f in glob.glob(os.path.join(g, so)):
554 module_dirs.setdefault(os.path.dirname(f), []).append(so)
555 for apache_module_path, modules_found in module_dirs.iteritems():
556 if len(modules_found) == len(apache_modules):
557 break
558 else:
559 # Appease cros lint, which doesn't understand that this else block will not
560 # fall through to the subsequent code which relies on apache_module_path.
561 apache_module_path = None
562 raise SystemExit(
563 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500564 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800565
566 def check_add_module(name):
567 so = 'mod_%s.so' % name
568 if os.access(os.path.join(apache_module_path, so), os.F_OK):
569 mod = '%s_module' % name
570 apache_modules.append((mod, so))
571 return True
572 return False
573
574 check_add_module('authz_core')
575 if may_need_mpm:
576 for mpm in PROXY_APACHE_MPMS:
577 if check_add_module('mpm_%s' % mpm):
578 break
579
580 veth_host = '%s-host' % PROXY_VETH_PREFIX
581 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
582
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500583 # Set up locks to sync the net namespace setup. We need the child to create
584 # the net ns first, and then have the parent assign the guest end of the veth
585 # interface to the child's new network namespace & bring up the proxy. Only
586 # then can the child move forward and rely on the network being up.
587 ns_create_lock = locking.PipeLock()
588 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800589
590 pid = os.fork()
591 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500592 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800593 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500594
595 # Signal the parent the ns is ready to be configured.
596 ns_create_lock.Post()
597 del ns_create_lock
598
599 # Wait for the parent to finish setting up the ns/proxy.
600 ns_setup_lock.Wait()
601 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800602
603 # Set up child side of the network.
604 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500605 ('ip', 'link', 'set', 'up', 'lo'),
606 ('ip', 'address', 'add',
607 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
608 'dev', veth_guest),
609 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800610 )
611 try:
612 for cmd in commands:
613 cros_build_lib.RunCommand(cmd, print_cmd=False)
614 except cros_build_lib.RunCommandError:
615 raise SystemExit('Running %r failed!' % (cmd,))
616
617 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
618 for proto in ('http', 'https', 'ftp'):
619 os.environ[proto + '_proxy'] = proxy_url
620 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
621 os.environ.pop(v, None)
622 return
623
Josh Triplett472a4182013-03-08 11:48:57 -0800624 # Set up parent side of the network.
625 uid = int(os.environ.get('SUDO_UID', '0'))
626 gid = int(os.environ.get('SUDO_GID', '0'))
627 if uid == 0 or gid == 0:
628 for username in PROXY_APACHE_FALLBACK_USERS:
629 try:
630 pwnam = pwd.getpwnam(username)
631 uid, gid = pwnam.pw_uid, pwnam.pw_gid
632 break
633 except KeyError:
634 continue
635 if uid == 0 or gid == 0:
636 raise SystemExit('Could not find a non-root user to run Apache as')
637
638 chroot_parent, chroot_base = os.path.split(options.chroot)
639 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
640 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
641
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500642 # Wait for the child to create the net ns.
643 ns_create_lock.Wait()
644 del ns_create_lock
645
Josh Triplett472a4182013-03-08 11:48:57 -0800646 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500647 'User #%u' % uid,
648 'Group #%u' % gid,
649 'PidFile %s' % pid_file,
650 'ErrorLog %s' % log_file,
651 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
652 'ServerName %s' % PROXY_HOST_IP,
653 'ProxyRequests On',
654 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800655 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500656 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
657 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800658 ]
659 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500660 ('ip', 'link', 'add', 'name', veth_host,
661 'type', 'veth', 'peer', 'name', veth_guest),
662 ('ip', 'address', 'add',
663 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
664 'dev', veth_host),
665 ('ip', 'link', 'set', veth_host, 'up'),
666 ([apache_bin, '-f', '/dev/null'] +
667 [arg for d in apache_directives for arg in ('-C', d)]),
668 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800669 )
670 cmd = None # Make cros lint happy.
671 try:
672 for cmd in commands:
673 cros_build_lib.RunCommand(cmd, print_cmd=False)
674 except cros_build_lib.RunCommandError:
675 # Clean up existing interfaces, if any.
676 cmd_cleanup = ('ip', 'link', 'del', veth_host)
677 try:
678 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
679 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700680 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800681 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500682
683 # Signal the child that the net ns/proxy is fully configured now.
684 ns_setup_lock.Post()
685 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800686
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400687 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800688
689
Mike Frysingera78a56e2012-11-20 06:02:30 -0500690def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700691 """Re-execute cros_sdk as root.
692
693 Also unshare the mount namespace so as to ensure that processes outside
694 the chroot can't mess with our mounts.
695 """
696 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500697 cmd = _SudoCommand() + ['--'] + argv
698 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500699 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400700 # We must set up the cgroups mounts before we enter our own namespace.
701 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800702 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700703
704
Mike Frysinger34db8692013-11-11 14:54:08 -0500705def _CreateParser(sdk_latest_version, bootstrap_latest_version):
706 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400707 usage = ('usage: %(prog)s [options] '
708 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
709 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
710 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700711
Mike Frysinger34db8692013-11-11 14:54:08 -0500712 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500713 default_chroot = os.path.join(constants.SOURCE_ROOT,
714 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400715 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700716 '--chroot', dest='chroot', default=default_chroot, type='path',
717 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600718 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
719 default=True,
720 help='Do not mount the chroot on a loopback image; '
721 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800722
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400723 parser.add_argument('--chrome_root', type='path',
724 help='Mount this chrome root into the SDK chroot')
725 parser.add_argument('--chrome_root_mount', type='path',
726 help='Mount chrome into this path inside SDK chroot')
727 parser.add_argument('--nousepkg', action='store_true', default=False,
728 help='Do not use binary packages when creating a chroot.')
729 parser.add_argument('-u', '--url', dest='sdk_url',
730 help='Use sdk tarball located at this url. Use file:// '
731 'for local files.')
732 parser.add_argument('--sdk-version',
733 help=('Use this sdk version. For prebuilt, current is %r'
734 ', for bootstrapping it is %r.'
735 % (sdk_latest_version, bootstrap_latest_version)))
736 parser.add_argument('--workspace',
737 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900738 parser.add_argument('--goma_dir', type='path',
739 help='Goma installed directory to mount into the chroot.')
740 parser.add_argument('--goma_client_json', type='path',
741 help='Service account json file to use goma on bot. '
742 'Mounted into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400743 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500744
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700745 # SDK overlay tarball options (mutually exclusive).
746 group = parser.add_mutually_exclusive_group()
747 group.add_argument('--toolchains',
748 help=('Comma-separated list of toolchains we expect to be '
749 'using on the chroot. Used for downloading a '
750 'corresponding SDK toolchains group (if one is '
751 'found), which may speed up chroot initialization '
752 'when building for the first time. Otherwise this '
753 'has no effect and will not restrict the chroot in '
754 'any way. Ignored if using --bootstrap.'))
755 group.add_argument('--board',
756 help=('The board we intend to be building in the chroot. '
757 'Used for deriving the list of required toolchains '
758 '(see --toolchains).'))
759
Mike Frysinger34db8692013-11-11 14:54:08 -0500760 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400761 group = parser.add_argument_group('Commands')
762 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500763 '--enter', action='store_true', default=False,
764 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400765 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500766 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500767 help='Create the chroot only if it does not already exist. '
768 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400769 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500770 '--bootstrap', action='store_true', default=False,
771 help='Build everything from scratch, including the sdk. '
772 'Use this only if you need to validate a change '
773 'that affects SDK creation itself (toolchain and '
774 'build are typically the only folk who need this). '
775 'Note this will quite heavily slow down the build. '
776 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400777 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500778 '-r', '--replace', action='store_true', default=False,
779 help='Replace an existing SDK chroot. Basically an alias '
780 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400781 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500782 '--delete', action='store_true', default=False,
783 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400784 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500785 '--download', action='store_true', default=False,
786 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600787 group.add_argument(
788 '--snapshot-create', metavar='SNAPSHOT_NAME',
789 help='Create a snapshot of the chroot. Requires that the chroot was '
790 'created without the --nouse-image option.')
791 group.add_argument(
792 '--snapshot-restore', metavar='SNAPSHOT_NAME',
793 help='Restore the chroot to a previously created snapshot.')
794 group.add_argument(
795 '--snapshot-delete', metavar='SNAPSHOT_NAME',
796 help='Delete a previously created snapshot. Deleting a snapshot that '
797 'does not exist is not an error.')
798 group.add_argument(
799 '--snapshot-list', action='store_true', default=False,
800 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500801 commands = group
802
Mike Frysinger80dfce92014-04-21 10:58:53 -0400803 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400804 group = parser.add_argument_group('Namespaces')
805 group.add_argument('--proxy-sim', action='store_true', default=False,
806 help='Simulate a restrictive network requiring an outbound'
807 ' proxy.')
808 group.add_argument('--no-ns-pid', dest='ns_pid',
809 default=True, action='store_false',
810 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400811
Mike Frysinger34db8692013-11-11 14:54:08 -0500812 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400813 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500814 'Internal Chromium OS Build Team Options',
815 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400816 group.add_argument('--buildbot-log-version', default=False,
817 action='store_true',
818 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500819
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400820 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500821
822
823def main(argv):
824 conf = cros_build_lib.LoadKeyValueFile(
825 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
826 ignore_missing=True)
827 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
828 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
829 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400830 options = parser.parse_args(argv)
831 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800832
833 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500834 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800835
Brian Harring1790ac42012-09-23 08:53:33 -0700836 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700837 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600838 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700839 "cros_sdk is currently only supported on x86_64; you're running"
840 " %s. Please find a x86_64 machine." % (host,))
841
Josh Triplett472a4182013-03-08 11:48:57 -0800842 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
843 if options.proxy_sim:
844 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600845 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800846
Benjamin Gordon040a1162017-06-29 13:44:47 -0600847 if (sdk_latest_version == '<unknown>' or
848 bootstrap_latest_version == '<unknown>'):
849 cros_build_lib.Die(
850 'No SDK version was found. '
851 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
852 'Please change to a directory inside your Chromium OS source tree\n'
853 'and retry. If you need to setup a Chromium OS source tree, see\n'
854 ' http://www.chromium.org/chromium-os/developer-guide')
855
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600856 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
857 or options.snapshot_delete or options.snapshot_list)
858 if any_snapshot_operation and not options.use_image:
859 cros_build_lib.Die('Snapshot operations are not compatible with '
860 '--nouse-image.')
861
862 if (options.snapshot_delete and options.snapshot_delete ==
863 options.snapshot_restore):
864 parser.error('Cannot --snapshot_delete the same snapshot you are '
865 'restoring with --snapshot_restore.')
866
David James471532c2013-01-21 10:23:31 -0800867 _ReExecuteIfNeeded([sys.argv[0]] + argv)
868
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600869 lock_path = os.path.dirname(options.chroot)
870 lock_path = os.path.join(
871 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
872
Brian Harring218e13c2012-10-10 16:21:26 -0700873 # Expand out the aliases...
874 if options.replace:
875 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800876
Brian Harring218e13c2012-10-10 16:21:26 -0700877 if options.bootstrap:
878 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800879
Brian Harring218e13c2012-10-10 16:21:26 -0700880 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400881 # pylint: disable=protected-access
882 # This _group_actions access sucks, but upstream decided to not include an
883 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700884 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400885 for x in commands._group_actions)
886 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700887 options.enter |= bool(chroot_command)
888
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600889 if (options.delete and not options.create and
890 (options.enter or any_snapshot_operation)):
891 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700892 "was specified makes no sense.")
893
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600894 # Clean up potential leftovers from previous interrupted builds.
895 # TODO(bmgordon): Remove this at the end of 2017. That should be long enough
896 # to get rid of them all.
897 chroot_build_path = options.chroot + '.build'
898 if options.use_image and os.path.exists(chroot_build_path):
899 try:
900 with cgroups.SimpleContainChildren('cros_sdk'):
901 with locking.FileLock(lock_path, 'chroot lock') as lock:
902 logging.notice('Cleaning up leftover build directory %s',
903 chroot_build_path)
904 lock.write_lock()
905 osutils.UmountTree(chroot_build_path)
906 osutils.RmDir(chroot_build_path)
907 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600908 logging.warning('Unable to remove %s: %s', chroot_build_path, e)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600909
Benjamin Gordon35194f12017-07-19 10:26:22 -0600910 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600911 chroot_ver_file = os.path.join(options.chroot, 'etc', 'cros_chroot_version')
912 chroot_exists = os.path.exists(chroot_ver_file)
913 if (options.use_image and not chroot_exists and not options.delete and
914 not missing_image_tools and
915 os.path.exists(_ImageFileForChroot(options.chroot))):
916 # Try to re-mount an existing image in case the user has rebooted.
917 with cgroups.SimpleContainChildren('cros_sdk'):
918 with locking.FileLock(lock_path, 'chroot lock') as lock:
919 logging.debug('Checking if existing chroot image can be mounted.')
920 lock.write_lock()
921 cros_build_lib.MountChroot(options.chroot, create=False)
922 chroot_exists = os.path.exists(chroot_ver_file)
923 if chroot_exists:
924 logging.notice('Mounted existing image %s on chroot',
925 _ImageFileForChroot(options.chroot))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600926 if (options.create or options.enter or options.snapshot_create or
927 options.snapshot_restore):
Brian Harring218e13c2012-10-10 16:21:26 -0700928 # Only create if it's being wiped, or if it doesn't exist.
929 if not options.delete and chroot_exists:
930 options.create = False
931 else:
932 options.download = True
933
934 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600935 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700936 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800937
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600938 # Anything that needs to manipulate the main chroot mount or communicate with
939 # LVM needs to be done here before we enter the new namespaces.
940
941 # If deleting, do it regardless of the use_image flag so that a
942 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600943 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
944 # favor of this block.
945 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600946 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600947 with cgroups.SimpleContainChildren('cros_sdk'):
948 with locking.FileLock(lock_path, 'chroot lock') as lock:
949 lock.write_lock()
950 if missing_image_tools:
951 logging.notice('Unmounting chroot.')
952 osutils.UmountTree(options.chroot)
953 else:
954 logging.notice('Deleting chroot.')
955 cros_build_lib.CleanupChrootMount(options.chroot, delete_image=True)
956 osutils.RmDir(options.chroot, ignore_missing=True)
957 chroot_deleted = True
958
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600959 # Make sure the main chroot mount is visible. Contents will be filled in
960 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600961 if options.create and options.use_image:
962 if missing_image_tools:
963 raise SystemExit(
964 '''The tool(s) %s were not found.
965Please make sure the lvm2 and thin-provisioning-tools packages
966are installed on your host.
967Example(ubuntu):
968 sudo apt-get install lvm2 thin-provisioning-tools
969
970If you want to run without lvm2, pass --nouse-image (chroot
971snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600972
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600973 logging.debug('Making sure chroot image is mounted.')
974 with cgroups.SimpleContainChildren('cros_sdk'):
975 with locking.FileLock(lock_path, 'chroot lock') as lock:
976 lock.write_lock()
977 if not cros_build_lib.MountChroot(options.chroot, create=True):
978 cros_build_lib.Die('Unable to mount %s on chroot',
979 _ImageFileForChroot(options.chroot))
980 logging.notice('Mounted %s on chroot',
981 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600982
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600983 # Snapshot operations will always need the VG/LV, but other actions won't.
984 if any_snapshot_operation:
985 with cgroups.SimpleContainChildren('cros_sdk'):
986 with locking.FileLock(lock_path, 'chroot lock') as lock:
987 chroot_vg, chroot_lv = cros_build_lib.FindChrootMountSource(
988 options.chroot)
989 if not chroot_vg or not chroot_lv:
990 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
991 options.chroot)
992
993 # Delete snapshot before creating a new one. This allows the user to
994 # throw out old state, create a new snapshot, and enter the chroot in a
995 # single call to cros_sdk. Since restore involves deleting, also do it
996 # before creating.
997 if options.snapshot_restore:
998 lock.write_lock()
999 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1000 if options.snapshot_restore not in valid_snapshots:
1001 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
1002 'Valid snapshots: %s', options.snapshot_restore,
1003 ', '.join(valid_snapshots))
1004 osutils.UmountTree(options.chroot)
1005 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1006 chroot_lv):
1007 cros_build_lib.Die('Unable to restore chroot to snapshot.')
1008 if not cros_build_lib.MountChroot(options.chroot, create=False):
1009 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1010
1011 # Use a read lock for snapshot delete and create even though they modify
1012 # the filesystem, because they don't modify the mounted chroot itself.
1013 # The underlying LVM commands take their own locks, so conflicting
1014 # concurrent operations here may crash cros_sdk, but won't corrupt the
1015 # chroot image. This tradeoff seems worth it to allow snapshot
1016 # operations on chroots that have a process inside.
1017 if options.snapshot_delete:
1018 lock.read_lock()
1019 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1020
1021 if options.snapshot_create:
1022 lock.read_lock()
1023 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1024 chroot_lv):
1025 cros_build_lib.Die('Unable to create snapshot.')
1026
1027 # Enter a new set of namespaces. Everything after here cannot directly affect
1028 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001029 namespaces.SimpleUnshare()
1030 if options.ns_pid:
1031 first_pid = namespaces.CreatePidNs()
1032 else:
1033 first_pid = None
1034
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001035 if options.snapshot_list:
1036 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1037 print(snap)
1038 sys.exit(0)
1039
Brian Harringb938c782012-02-29 15:14:38 -08001040 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001041 sdk_version = (bootstrap_latest_version if options.bootstrap
1042 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001043 else:
1044 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001045 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001046 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001047
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001048 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -07001049 if options.sdk_url:
1050 urls = [options.sdk_url]
1051 elif options.bootstrap:
1052 urls = GetStage3Urls(sdk_version)
1053 else:
1054 urls = GetArchStageTarballs(sdk_version)
1055
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001056 # Get URLs for the toolchains overlay, if one is to be used.
1057 toolchains_overlay_urls = None
1058 if not options.bootstrap:
1059 toolchains = None
1060 if options.toolchains:
1061 toolchains = options.toolchains.split(',')
1062 elif options.board:
1063 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
1064
1065 if toolchains:
1066 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
1067 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001068
Mike Frysinger80dfce92014-04-21 10:58:53 -04001069 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001070 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001071 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -07001072
Josh Triplett472a4182013-03-08 11:48:57 -08001073 if options.proxy_sim:
1074 _ProxySimSetup(options)
1075
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001076 if (options.delete and not chroot_deleted and
1077 (os.path.exists(options.chroot) or
1078 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001079 lock.write_lock()
1080 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001081
David James56e6c2c2012-10-24 23:54:41 -07001082 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1083 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001084 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001085
David James56e6c2c2012-10-24 23:54:41 -07001086 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001087 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001088 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001089 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001090 continue
1091 lock.write_lock(
1092 "Upgrade to %r needed but chroot is locked; please exit "
1093 "all instances so this upgrade can finish." % src)
1094 if not os.path.exists(src):
1095 # Note that while waiting for the write lock, src may've vanished;
1096 # it's a rare race during the upgrade process that's a byproduct
1097 # of us avoiding taking a write lock to do the src check. If we
1098 # took a write lock for that check, it would effectively limit
1099 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001100 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001101 elif not os.path.exists(target):
1102 # Upgrade occurred, but a reversion, or something whacky
1103 # occurred writing to the old location. Wipe and continue.
1104 os.rename(src, target)
1105 else:
1106 # Upgrade occurred once already, but either a reversion or
1107 # some before/after separate cros_sdk usage is at play.
1108 # Wipe and continue.
1109 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001110
David James56e6c2c2012-10-24 23:54:41 -07001111 if options.download:
1112 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001113 sdk_tarball = FetchRemoteTarballs(
1114 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
1115 if toolchains_overlay_urls:
1116 toolchains_overlay_tarball = FetchRemoteTarballs(
1117 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
1118 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -07001119
David James56e6c2c2012-10-24 23:54:41 -07001120 if options.create:
1121 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001122 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001123 options.cache_dir,
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001124 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001125
David James56e6c2c2012-10-24 23:54:41 -07001126 if options.enter:
1127 lock.read_lock()
1128 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001129 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001130 options.goma_dir, options.goma_client_json,
Don Garrett230d1b22015-03-09 16:21:19 -07001131 chroot_command)