blob: 3d2ae108e8a5db8c91912a36e217ea691bc2e746 [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.
5
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
Brian Harringb938c782012-02-29 15:14:38 -080032from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080033from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070034from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040035from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080036from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050037from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080038
39cros_build_lib.STRICT_SUDO = True
40
41
Zdenek Behanaa52cea2012-05-30 01:31:11 +020042COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020043
Brian Harringb938c782012-02-29 15:14:38 -080044# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050045MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
46 'src/scripts/sdk_lib/make_chroot.sh')]
47ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
48 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080049
Josh Triplett472a4182013-03-08 11:48:57 -080050# Proxy simulator configuration.
51PROXY_HOST_IP = '192.168.240.1'
52PROXY_PORT = 8080
53PROXY_GUEST_IP = '192.168.240.2'
54PROXY_NETMASK = 30
55PROXY_VETH_PREFIX = 'veth'
56PROXY_CONNECT_PORTS = (80, 443, 9418)
57PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
58PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
59PROXY_APACHE_FALLBACK_PATH = ':'.join(
60 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
61)
62PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
63
Josh Triplett9a495f62013-03-15 18:06:55 -070064# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080065NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080066
Josh Triplett472a4182013-03-08 11:48:57 -080067# Tools needed for --proxy-sim only.
68PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080069
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060070# Tools needed when use_image is true (the default).
71IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060072 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060073
Mike Frysingercc838832014-05-24 13:10:30 -040074
Brian Harring1790ac42012-09-23 08:53:33 -070075def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080076 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070077 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050078 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
79 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070080 for compressor in COMPRESSION_PREFERENCE]
81
82
83def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050084 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070085 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080086
87
Gilad Arnold6a8f0452015-06-04 11:25:18 -070088def GetToolchainsOverlayUrls(version, toolchains):
89 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070090
91 Args:
92 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
93 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -070094 overlays are stored (.../2015/05/ in this case).
95 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070096
97 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -070098 List of alternative download URLs for an SDK overlay tarball that contains
99 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700100 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700101 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700102 suburl_template = os.path.join(
103 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700104 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
105 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700106 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
107 for ext in COMPRESSION_PREFERENCE]
108
109
110def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500111 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112
113 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500114 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200115 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700116 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700117 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200118
119 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700120 Full path to the downloaded file, or None if |allow_none| and no URL worked.
121
122 Raises:
123 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200124 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200125
Brian Harring1790ac42012-09-23 08:53:33 -0700126 # Note we track content length ourselves since certain versions of curl
127 # fail if asked to resume a complete file.
128 # pylint: disable=C0301,W0631
129 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700130 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700131 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200132 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700133 # http://www.logilab.org/ticket/8766
134 # pylint: disable=E1101
135 parsed = urlparse.urlparse(url)
136 tarball_name = os.path.basename(parsed.path)
137 if parsed.scheme in ('', 'file'):
138 if os.path.exists(parsed.path):
139 return parsed.path
140 continue
141 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700142 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800143 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900144 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
145 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700146 successful = False
147 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700148 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700149 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700150 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700151 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500152 elif header.lower().startswith('content-length:'):
153 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700154 if successful:
155 break
156 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200157 break
158 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700159 if allow_none:
160 return None
161 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200162
Brian Harringae0a5322012-09-15 01:46:51 -0700163 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700164 current_size = 0
165 if os.path.exists(tarball_dest):
166 current_size = os.path.getsize(tarball_dest)
167 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700168 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700169 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100170
Brian Harring1790ac42012-09-23 08:53:33 -0700171 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800172 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900173 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
174 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800175
Brian Harring1790ac42012-09-23 08:53:33 -0700176 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700177 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
178 # because we might have partial overlap between known prefixes.
179 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
180 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
181 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700182 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700183 if (filename == tarball_name or
184 any([(filename.startswith(p) and
185 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
186 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700187 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700188 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700189 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200190
Brian Harringb938c782012-02-29 15:14:38 -0800191 return tarball_dest
192
193
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700194def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600195 cache_dir, nousepkg=False):
196 """Creates a new chroot from a given SDK.
197
198 Args:
199 chroot_path: Path where the new chroot will be created.
200 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
201 toolchains_overlay_tarball: Optional path to a second tarball that will be
202 unpacked into the chroot on top of the SDK tarball.
203 cache_dir: Path to a directory that will be used for caching portage files,
204 etc.
205 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
206 chroot.
207 """
Brian Harringb938c782012-02-29 15:14:38 -0800208
Brian Harring1790ac42012-09-23 08:53:33 -0700209 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700210 '--chroot', chroot_path,
211 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700212
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700213 if toolchains_overlay_tarball:
214 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700215
Mike Frysinger2de7f042012-07-10 04:45:03 -0400216 if nousepkg:
217 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800218
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700219 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800220 try:
221 cros_build_lib.RunCommand(cmd, print_cmd=False)
222 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700223 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800224
225
226def DeleteChroot(chroot_path):
227 """Deletes an existing chroot"""
228 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
229 '--delete']
230 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700231 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800232 cros_build_lib.RunCommand(cmd, print_cmd=False)
233 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700234 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800235
236
Brian Harringae0a5322012-09-15 01:46:51 -0700237def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900238 workspace, goma_dir, goma_client_json, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800239 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400240 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
241 # The os.ST_NOSUID constant wasn't added until python-3.2.
242 if st.f_flag & 0x2:
243 cros_build_lib.Die('chroot cannot be in a nosuid mount')
244
Brian Harringae0a5322012-09-15 01:46:51 -0700245 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800246 if chrome_root:
247 cmd.extend(['--chrome_root', chrome_root])
248 if chrome_root_mount:
249 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700250 if workspace:
251 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900252 if goma_dir:
253 cmd.extend(['--goma_dir', goma_dir])
254 if goma_client_json:
255 cmd.extend(['--goma_client_json', goma_client_json])
Don Garrett230d1b22015-03-09 16:21:19 -0700256
Brian Harringb938c782012-02-29 15:14:38 -0800257 if len(additional_args) > 0:
258 cmd.append('--')
259 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700260
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700261 # ThinLTO opens lots of files at the same time.
262 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700263 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
264 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700265 # If we were in interactive mode, ignore the exit code; it'll be whatever
266 # they last ran w/in the chroot and won't matter to us one way or another.
267 # Note this does allow chroot entrance to fail and be ignored during
268 # interactive; this is however a rare case and the user will immediately
269 # see it (nor will they be checking the exit code manually).
270 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700271 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800272
273
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600274def _ImageFileForChroot(chroot):
275 """Find the image file that should be associated with |chroot|.
276
277 This function does not check if the image exists; it simply returns the
278 filename that would be used.
279
280 Args:
281 chroot: Path to the chroot.
282
283 Returns:
284 Path to an image file that would be associated with chroot.
285 """
286 return chroot.rstrip('/') + '.img'
287
288
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600289def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
290 """Create a snapshot for the specified chroot VG/LV.
291
292 Args:
293 snapshot_name: The name of the new snapshot.
294 chroot_vg: The name of the VG containing the origin LV.
295 chroot_lv: The name of the origin LV.
296
297 Returns:
298 True if the snapshot was created, or False if a snapshot with the same
299 name already exists.
300
301 Raises:
302 SystemExit: The lvcreate command failed.
303 """
304 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
305 logging.error('Cannot create snapshot %s: A volume with that name already '
306 'exists.', snapshot_name)
307 return False
308
309 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
310 chroot_vg, chroot_lv)]
311 try:
312 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
313 chroot_lv, chroot_vg)
314 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
315 return True
316 except cros_build_lib.RunCommandError:
317 raise SystemExit('Running %r failed!' % cmd)
318
319
320def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
321 """Delete the named snapshot from the specified chroot VG.
322
323 If the requested snapshot is not found, nothing happens. The main chroot LV
324 and internal thinpool LV cannot be deleted with this function.
325
326 Args:
327 snapshot_name: The name of the snapshot to delete.
328 chroot_vg: The name of the VG containing the origin LV.
329 chroot_lv: The name of the origin LV.
330
331 Raises:
332 SystemExit: The lvremove command failed.
333 """
334 if snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
335 cros_build_lib.CHROOT_THINPOOL_NAME):
336 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
337 'if you want to remove the whole chroot.', snapshot_name)
338 return
339
340 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
341 return
342
343 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
344 try:
345 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
346 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
347 except cros_build_lib.RunCommandError:
348 raise SystemExit('Running %r failed!' % cmd)
349
350
351def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
352 """Restore the chroot to an existing snapshot.
353
354 This is done by renaming the original |chroot_lv| LV to a temporary name,
355 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
356 now unused LV. If an error occurs, attempts to rename the original snapshot
357 back to |chroot_lv| to leave the chroot unchanged.
358
359 The chroot must be unmounted before calling this function, and will be left
360 unmounted after this function returns.
361
362 Args:
363 snapshot_name: The name of the snapshot to restore. This snapshot will no
364 longer be accessible at its original name after this function finishes.
365 chroot_vg: The VG containing the chroot LV and snapshot LV.
366 chroot_lv: The name of the original chroot LV.
367
368 Returns:
369 True if the chroot was restored to the requested snapshot, or False if
370 the snapshot wasn't found or isn't valid.
371
372 Raises:
373 SystemExit: Any of the LVM commands failed.
374 """
375 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
376 if (snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
377 cros_build_lib.CHROOT_THINPOOL_NAME) or
378 snapshot_name not in valid_snapshots):
379 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
380 snapshot_name, ', '.join(valid_snapshots))
381 return False
382
383 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
384 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
385 try:
386 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
387 except cros_build_lib.RunCommandError:
388 raise SystemExit('Running %r failed!' % cmd)
389
390 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
391 try:
392 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
393 except cros_build_lib.RunCommandError:
394 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
395 try:
396 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
397 except cros_build_lib.RunCommandError:
398 raise SystemExit('Failed to rename %s to chroot and failed to restore '
399 '%s back to chroot. Failed command: %r' %
400 (snapshot_name, backup_chroot_name, cmd))
401 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
402 'been restored. Failed command: %r' %
403 (snapshot_name, cmd))
404
405 # Some versions of LVM set snapshots to be skipped at auto-activate time.
406 # Other versions don't have this flag at all. We run lvchange to try
407 # disabling auto-skip and activating the volume, but ignore errors. Versions
408 # that don't have the flag should be auto-activated.
409 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
410 cmd = ['lvchange', '-kn', chroot_lv_path]
411 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
412 error_code_ok=True)
413
414 # Activate the LV in case the lvchange above was needed. Activating an LV
415 # that is already active shouldn't do anything, so this is safe to run even if
416 # the -kn wasn't needed.
417 cmd = ['lvchange', '-ay', chroot_lv_path]
418 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
419
420 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
421 try:
422 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
423 except cros_build_lib.RunCommandError:
424 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
425 (chroot_vg, backup_chroot_name, cmd))
426
427 return True
428
429
430def ListChrootSnapshots(chroot_vg, chroot_lv):
431 """Return all snapshots in |chroot_vg| regardless of origin volume.
432
433 Args:
434 chroot_vg: The name of the VG containing the chroot.
435 chroot_lv: The name of the chroot LV.
436
437 Returns:
438 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
439
440 Raises:
441 SystemExit: The lvs command failed.
442 """
443 if not chroot_vg or not chroot_lv:
444 return []
445
446 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
447 '--noheadings', '--separator', '\t', chroot_vg]
448 try:
449 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
450 redirect_stdout=True)
451 except cros_build_lib.RunCommandError:
452 raise SystemExit('Running %r failed!' % cmd)
453
454 # Once the thin origin volume has been deleted, there's no way to tell a
455 # snapshot apart from any other volume. Since this VG is created and managed
456 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
457 # valid snapshots.
458 snapshots = []
459 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
460 for line in result.output.splitlines():
461 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
462 if (lv_name == chroot_lv or
463 lv_name == cros_build_lib.CHROOT_THINPOOL_NAME or
464 pool_lv != cros_build_lib.CHROOT_THINPOOL_NAME or
465 not snapshot_attrs.match(lv_attr)):
466 continue
467 snapshots.append(lv_name)
468 return snapshots
469
470
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600471def _FindSubmounts(*args):
472 """Find all mounts matching each of the paths in |args| and any submounts.
473
474 Returns:
475 A list of all matching mounts in the order found in /proc/mounts.
476 """
477 mounts = []
478 paths = [p.rstrip('/') for p in args]
479 for mtab in osutils.IterateMountPoints():
480 for path in paths:
481 if mtab.destination == path or mtab.destination.startswith(path + '/'):
482 mounts.append(mtab.destination)
483 break
484
485 return mounts
486
487
David James56e6c2c2012-10-24 23:54:41 -0700488def _SudoCommand():
489 """Get the 'sudo' command, along with all needed environment variables."""
490
David James5a73b4d2013-03-07 10:23:40 -0800491 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
492 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700493 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800494 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700495 value = os.environ.get(key)
496 if value is not None:
497 cmd += ['%s=%s' % (key, value)]
498
499 # Pass in the path to the depot_tools so that users can access them from
500 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400501 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500502
David James56e6c2c2012-10-24 23:54:41 -0700503 return cmd
504
505
Josh Triplett472a4182013-03-08 11:48:57 -0800506def _ReportMissing(missing):
507 """Report missing utilities, then exit.
508
509 Args:
510 missing: List of missing utilities, as returned by
511 osutils.FindMissingBinaries. If non-empty, will not return.
512 """
513
514 if missing:
515 raise SystemExit(
516 'The tool(s) %s were not found.\n'
517 'Please install the appropriate package in your host.\n'
518 'Example(ubuntu):\n'
519 ' sudo apt-get install <packagename>'
520 % ', '.join(missing))
521
522
523def _ProxySimSetup(options):
524 """Set up proxy simulator, and return only in the child environment.
525
526 TODO: Ideally, this should support multiple concurrent invocations of
527 cros_sdk --proxy-sim; currently, such invocations will conflict with each
528 other due to the veth device names and IP addresses. Either this code would
529 need to generate fresh, unused names for all of these before forking, or it
530 would need to support multiple concurrent cros_sdk invocations sharing one
531 proxy and allowing it to exit when unused (without counting on any local
532 service-management infrastructure on the host).
533 """
534
535 may_need_mpm = False
536 apache_bin = osutils.Which('apache2')
537 if apache_bin is None:
538 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
539 if apache_bin is None:
540 _ReportMissing(('apache2',))
541 else:
542 may_need_mpm = True
543
544 # Module names and .so names included for ease of grepping.
545 apache_modules = [('proxy_module', 'mod_proxy.so'),
546 ('proxy_connect_module', 'mod_proxy_connect.so'),
547 ('proxy_http_module', 'mod_proxy_http.so'),
548 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
549
550 # Find the apache module directory, and make sure it has the modules we need.
551 module_dirs = {}
552 for g in PROXY_APACHE_MODULE_GLOBS:
553 for mod, so in apache_modules:
554 for f in glob.glob(os.path.join(g, so)):
555 module_dirs.setdefault(os.path.dirname(f), []).append(so)
556 for apache_module_path, modules_found in module_dirs.iteritems():
557 if len(modules_found) == len(apache_modules):
558 break
559 else:
560 # Appease cros lint, which doesn't understand that this else block will not
561 # fall through to the subsequent code which relies on apache_module_path.
562 apache_module_path = None
563 raise SystemExit(
564 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500565 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800566
567 def check_add_module(name):
568 so = 'mod_%s.so' % name
569 if os.access(os.path.join(apache_module_path, so), os.F_OK):
570 mod = '%s_module' % name
571 apache_modules.append((mod, so))
572 return True
573 return False
574
575 check_add_module('authz_core')
576 if may_need_mpm:
577 for mpm in PROXY_APACHE_MPMS:
578 if check_add_module('mpm_%s' % mpm):
579 break
580
581 veth_host = '%s-host' % PROXY_VETH_PREFIX
582 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
583
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500584 # Set up locks to sync the net namespace setup. We need the child to create
585 # the net ns first, and then have the parent assign the guest end of the veth
586 # interface to the child's new network namespace & bring up the proxy. Only
587 # then can the child move forward and rely on the network being up.
588 ns_create_lock = locking.PipeLock()
589 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800590
591 pid = os.fork()
592 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500593 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800594 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500595
596 # Signal the parent the ns is ready to be configured.
597 ns_create_lock.Post()
598 del ns_create_lock
599
600 # Wait for the parent to finish setting up the ns/proxy.
601 ns_setup_lock.Wait()
602 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800603
604 # Set up child side of the network.
605 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500606 ('ip', 'link', 'set', 'up', 'lo'),
607 ('ip', 'address', 'add',
608 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
609 'dev', veth_guest),
610 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800611 )
612 try:
613 for cmd in commands:
614 cros_build_lib.RunCommand(cmd, print_cmd=False)
615 except cros_build_lib.RunCommandError:
616 raise SystemExit('Running %r failed!' % (cmd,))
617
618 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
619 for proto in ('http', 'https', 'ftp'):
620 os.environ[proto + '_proxy'] = proxy_url
621 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
622 os.environ.pop(v, None)
623 return
624
Josh Triplett472a4182013-03-08 11:48:57 -0800625 # Set up parent side of the network.
626 uid = int(os.environ.get('SUDO_UID', '0'))
627 gid = int(os.environ.get('SUDO_GID', '0'))
628 if uid == 0 or gid == 0:
629 for username in PROXY_APACHE_FALLBACK_USERS:
630 try:
631 pwnam = pwd.getpwnam(username)
632 uid, gid = pwnam.pw_uid, pwnam.pw_gid
633 break
634 except KeyError:
635 continue
636 if uid == 0 or gid == 0:
637 raise SystemExit('Could not find a non-root user to run Apache as')
638
639 chroot_parent, chroot_base = os.path.split(options.chroot)
640 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
641 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
642
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500643 # Wait for the child to create the net ns.
644 ns_create_lock.Wait()
645 del ns_create_lock
646
Josh Triplett472a4182013-03-08 11:48:57 -0800647 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500648 'User #%u' % uid,
649 'Group #%u' % gid,
650 'PidFile %s' % pid_file,
651 'ErrorLog %s' % log_file,
652 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
653 'ServerName %s' % PROXY_HOST_IP,
654 'ProxyRequests On',
655 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800656 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500657 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
658 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800659 ]
660 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500661 ('ip', 'link', 'add', 'name', veth_host,
662 'type', 'veth', 'peer', 'name', veth_guest),
663 ('ip', 'address', 'add',
664 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
665 'dev', veth_host),
666 ('ip', 'link', 'set', veth_host, 'up'),
667 ([apache_bin, '-f', '/dev/null'] +
668 [arg for d in apache_directives for arg in ('-C', d)]),
669 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800670 )
671 cmd = None # Make cros lint happy.
672 try:
673 for cmd in commands:
674 cros_build_lib.RunCommand(cmd, print_cmd=False)
675 except cros_build_lib.RunCommandError:
676 # Clean up existing interfaces, if any.
677 cmd_cleanup = ('ip', 'link', 'del', veth_host)
678 try:
679 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
680 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700681 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800682 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500683
684 # Signal the child that the net ns/proxy is fully configured now.
685 ns_setup_lock.Post()
686 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800687
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400688 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800689
690
Mike Frysingera78a56e2012-11-20 06:02:30 -0500691def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700692 """Re-execute cros_sdk as root.
693
694 Also unshare the mount namespace so as to ensure that processes outside
695 the chroot can't mess with our mounts.
696 """
697 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500698 cmd = _SudoCommand() + ['--'] + argv
699 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500700 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400701 # We must set up the cgroups mounts before we enter our own namespace.
702 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800703 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700704
705
Mike Frysinger34db8692013-11-11 14:54:08 -0500706def _CreateParser(sdk_latest_version, bootstrap_latest_version):
707 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400708 usage = ('usage: %(prog)s [options] '
709 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
710 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
711 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700712
Mike Frysinger34db8692013-11-11 14:54:08 -0500713 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500714 default_chroot = os.path.join(constants.SOURCE_ROOT,
715 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400716 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700717 '--chroot', dest='chroot', default=default_chroot, type='path',
718 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600719 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
720 default=True,
721 help='Do not mount the chroot on a loopback image; '
722 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800723
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400724 parser.add_argument('--chrome_root', type='path',
725 help='Mount this chrome root into the SDK chroot')
726 parser.add_argument('--chrome_root_mount', type='path',
727 help='Mount chrome into this path inside SDK chroot')
728 parser.add_argument('--nousepkg', action='store_true', default=False,
729 help='Do not use binary packages when creating a chroot.')
730 parser.add_argument('-u', '--url', dest='sdk_url',
731 help='Use sdk tarball located at this url. Use file:// '
732 'for local files.')
733 parser.add_argument('--sdk-version',
734 help=('Use this sdk version. For prebuilt, current is %r'
735 ', for bootstrapping it is %r.'
736 % (sdk_latest_version, bootstrap_latest_version)))
737 parser.add_argument('--workspace',
738 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900739 parser.add_argument('--goma_dir', type='path',
740 help='Goma installed directory to mount into the chroot.')
741 parser.add_argument('--goma_client_json', type='path',
742 help='Service account json file to use goma on bot. '
743 'Mounted into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400744 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500745
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700746 # SDK overlay tarball options (mutually exclusive).
747 group = parser.add_mutually_exclusive_group()
748 group.add_argument('--toolchains',
749 help=('Comma-separated list of toolchains we expect to be '
750 'using on the chroot. Used for downloading a '
751 'corresponding SDK toolchains group (if one is '
752 'found), which may speed up chroot initialization '
753 'when building for the first time. Otherwise this '
754 'has no effect and will not restrict the chroot in '
755 'any way. Ignored if using --bootstrap.'))
756 group.add_argument('--board',
757 help=('The board we intend to be building in the chroot. '
758 'Used for deriving the list of required toolchains '
759 '(see --toolchains).'))
760
Mike Frysinger34db8692013-11-11 14:54:08 -0500761 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400762 group = parser.add_argument_group('Commands')
763 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500764 '--enter', action='store_true', default=False,
765 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400766 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500767 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500768 help='Create the chroot only if it does not already exist. '
769 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400770 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500771 '--bootstrap', action='store_true', default=False,
772 help='Build everything from scratch, including the sdk. '
773 'Use this only if you need to validate a change '
774 'that affects SDK creation itself (toolchain and '
775 'build are typically the only folk who need this). '
776 'Note this will quite heavily slow down the build. '
777 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400778 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500779 '-r', '--replace', action='store_true', default=False,
780 help='Replace an existing SDK chroot. Basically an alias '
781 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400782 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500783 '--delete', action='store_true', default=False,
784 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400785 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500786 '--download', action='store_true', default=False,
787 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600788 group.add_argument(
789 '--snapshot-create', metavar='SNAPSHOT_NAME',
790 help='Create a snapshot of the chroot. Requires that the chroot was '
791 'created without the --nouse-image option.')
792 group.add_argument(
793 '--snapshot-restore', metavar='SNAPSHOT_NAME',
794 help='Restore the chroot to a previously created snapshot.')
795 group.add_argument(
796 '--snapshot-delete', metavar='SNAPSHOT_NAME',
797 help='Delete a previously created snapshot. Deleting a snapshot that '
798 'does not exist is not an error.')
799 group.add_argument(
800 '--snapshot-list', action='store_true', default=False,
801 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500802 commands = group
803
Mike Frysinger80dfce92014-04-21 10:58:53 -0400804 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400805 group = parser.add_argument_group('Namespaces')
806 group.add_argument('--proxy-sim', action='store_true', default=False,
807 help='Simulate a restrictive network requiring an outbound'
808 ' proxy.')
809 group.add_argument('--no-ns-pid', dest='ns_pid',
810 default=True, action='store_false',
811 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400812
Mike Frysinger34db8692013-11-11 14:54:08 -0500813 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400814 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500815 'Internal Chromium OS Build Team Options',
816 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400817 group.add_argument('--buildbot-log-version', default=False,
818 action='store_true',
819 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500820
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400821 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500822
823
824def main(argv):
825 conf = cros_build_lib.LoadKeyValueFile(
826 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
827 ignore_missing=True)
828 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
829 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
830 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400831 options = parser.parse_args(argv)
832 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800833
834 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500835 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800836
Brian Harring1790ac42012-09-23 08:53:33 -0700837 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700838 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600839 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700840 "cros_sdk is currently only supported on x86_64; you're running"
841 " %s. Please find a x86_64 machine." % (host,))
842
Josh Triplett472a4182013-03-08 11:48:57 -0800843 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
844 if options.proxy_sim:
845 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600846 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800847
Benjamin Gordon040a1162017-06-29 13:44:47 -0600848 if (sdk_latest_version == '<unknown>' or
849 bootstrap_latest_version == '<unknown>'):
850 cros_build_lib.Die(
851 'No SDK version was found. '
852 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
853 'Please change to a directory inside your Chromium OS source tree\n'
854 'and retry. If you need to setup a Chromium OS source tree, see\n'
855 ' http://www.chromium.org/chromium-os/developer-guide')
856
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600857 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
858 or options.snapshot_delete or options.snapshot_list)
859 if any_snapshot_operation and not options.use_image:
860 cros_build_lib.Die('Snapshot operations are not compatible with '
861 '--nouse-image.')
862
863 if (options.snapshot_delete and options.snapshot_delete ==
864 options.snapshot_restore):
865 parser.error('Cannot --snapshot_delete the same snapshot you are '
866 'restoring with --snapshot_restore.')
867
David James471532c2013-01-21 10:23:31 -0800868 _ReExecuteIfNeeded([sys.argv[0]] + argv)
869
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600870 lock_path = os.path.dirname(options.chroot)
871 lock_path = os.path.join(
872 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
873
Brian Harring218e13c2012-10-10 16:21:26 -0700874 # Expand out the aliases...
875 if options.replace:
876 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800877
Brian Harring218e13c2012-10-10 16:21:26 -0700878 if options.bootstrap:
879 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800880
Brian Harring218e13c2012-10-10 16:21:26 -0700881 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400882 # pylint: disable=protected-access
883 # This _group_actions access sucks, but upstream decided to not include an
884 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700885 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400886 for x in commands._group_actions)
887 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700888 options.enter |= bool(chroot_command)
889
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600890 if (options.delete and not options.create and
891 (options.enter or any_snapshot_operation)):
892 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700893 "was specified makes no sense.")
894
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600895 # Clean up potential leftovers from previous interrupted builds.
896 # TODO(bmgordon): Remove this at the end of 2017. That should be long enough
897 # to get rid of them all.
898 chroot_build_path = options.chroot + '.build'
899 if options.use_image and os.path.exists(chroot_build_path):
900 try:
901 with cgroups.SimpleContainChildren('cros_sdk'):
902 with locking.FileLock(lock_path, 'chroot lock') as lock:
903 logging.notice('Cleaning up leftover build directory %s',
904 chroot_build_path)
905 lock.write_lock()
906 osutils.UmountTree(chroot_build_path)
907 osutils.RmDir(chroot_build_path)
908 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600909 logging.warning('Unable to remove %s: %s', chroot_build_path, e)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600910
Benjamin Gordon35194f12017-07-19 10:26:22 -0600911 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600912 chroot_ver_file = os.path.join(options.chroot, 'etc', 'cros_chroot_version')
913 chroot_exists = os.path.exists(chroot_ver_file)
914 if (options.use_image and not chroot_exists and not options.delete and
915 not missing_image_tools and
916 os.path.exists(_ImageFileForChroot(options.chroot))):
917 # Try to re-mount an existing image in case the user has rebooted.
918 with cgroups.SimpleContainChildren('cros_sdk'):
919 with locking.FileLock(lock_path, 'chroot lock') as lock:
920 logging.debug('Checking if existing chroot image can be mounted.')
921 lock.write_lock()
922 cros_build_lib.MountChroot(options.chroot, create=False)
923 chroot_exists = os.path.exists(chroot_ver_file)
924 if chroot_exists:
925 logging.notice('Mounted existing image %s on chroot',
926 _ImageFileForChroot(options.chroot))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600927 if (options.create or options.enter or options.snapshot_create or
928 options.snapshot_restore):
Brian Harring218e13c2012-10-10 16:21:26 -0700929 # Only create if it's being wiped, or if it doesn't exist.
930 if not options.delete and chroot_exists:
931 options.create = False
932 else:
933 options.download = True
934
935 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600936 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700937 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800938
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600939 # Anything that needs to manipulate the main chroot mount or communicate with
940 # LVM needs to be done here before we enter the new namespaces.
941
942 # If deleting, do it regardless of the use_image flag so that a
943 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600944 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
945 # favor of this block.
946 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600947 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600948 with cgroups.SimpleContainChildren('cros_sdk'):
949 with locking.FileLock(lock_path, 'chroot lock') as lock:
950 lock.write_lock()
951 if missing_image_tools:
952 logging.notice('Unmounting chroot.')
953 osutils.UmountTree(options.chroot)
954 else:
955 logging.notice('Deleting chroot.')
956 cros_build_lib.CleanupChrootMount(options.chroot, delete_image=True)
957 osutils.RmDir(options.chroot, ignore_missing=True)
958 chroot_deleted = True
959
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600960 # Make sure the main chroot mount is visible. Contents will be filled in
961 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600962 if options.create and options.use_image:
963 if missing_image_tools:
964 raise SystemExit(
965 '''The tool(s) %s were not found.
966Please make sure the lvm2 and thin-provisioning-tools packages
967are installed on your host.
968Example(ubuntu):
969 sudo apt-get install lvm2 thin-provisioning-tools
970
971If you want to run without lvm2, pass --nouse-image (chroot
972snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600973
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600974 logging.debug('Making sure chroot image is mounted.')
975 with cgroups.SimpleContainChildren('cros_sdk'):
976 with locking.FileLock(lock_path, 'chroot lock') as lock:
977 lock.write_lock()
978 if not cros_build_lib.MountChroot(options.chroot, create=True):
979 cros_build_lib.Die('Unable to mount %s on chroot',
980 _ImageFileForChroot(options.chroot))
981 logging.notice('Mounted %s on chroot',
982 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600983
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600984 # Snapshot operations will always need the VG/LV, but other actions won't.
985 if any_snapshot_operation:
986 with cgroups.SimpleContainChildren('cros_sdk'):
987 with locking.FileLock(lock_path, 'chroot lock') as lock:
988 chroot_vg, chroot_lv = cros_build_lib.FindChrootMountSource(
989 options.chroot)
990 if not chroot_vg or not chroot_lv:
991 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
992 options.chroot)
993
994 # Delete snapshot before creating a new one. This allows the user to
995 # throw out old state, create a new snapshot, and enter the chroot in a
996 # single call to cros_sdk. Since restore involves deleting, also do it
997 # before creating.
998 if options.snapshot_restore:
999 lock.write_lock()
1000 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1001 if options.snapshot_restore not in valid_snapshots:
1002 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
1003 'Valid snapshots: %s', options.snapshot_restore,
1004 ', '.join(valid_snapshots))
1005 osutils.UmountTree(options.chroot)
1006 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1007 chroot_lv):
1008 cros_build_lib.Die('Unable to restore chroot to snapshot.')
1009 if not cros_build_lib.MountChroot(options.chroot, create=False):
1010 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1011
1012 # Use a read lock for snapshot delete and create even though they modify
1013 # the filesystem, because they don't modify the mounted chroot itself.
1014 # The underlying LVM commands take their own locks, so conflicting
1015 # concurrent operations here may crash cros_sdk, but won't corrupt the
1016 # chroot image. This tradeoff seems worth it to allow snapshot
1017 # operations on chroots that have a process inside.
1018 if options.snapshot_delete:
1019 lock.read_lock()
1020 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1021
1022 if options.snapshot_create:
1023 lock.read_lock()
1024 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1025 chroot_lv):
1026 cros_build_lib.Die('Unable to create snapshot.')
1027
1028 # Enter a new set of namespaces. Everything after here cannot directly affect
1029 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001030 namespaces.SimpleUnshare()
1031 if options.ns_pid:
1032 first_pid = namespaces.CreatePidNs()
1033 else:
1034 first_pid = None
1035
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001036 if options.snapshot_list:
1037 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1038 print(snap)
1039 sys.exit(0)
1040
Brian Harringb938c782012-02-29 15:14:38 -08001041 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001042 sdk_version = (bootstrap_latest_version if options.bootstrap
1043 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001044 else:
1045 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001046 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001047 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001048
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001049 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -07001050 if options.sdk_url:
1051 urls = [options.sdk_url]
1052 elif options.bootstrap:
1053 urls = GetStage3Urls(sdk_version)
1054 else:
1055 urls = GetArchStageTarballs(sdk_version)
1056
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001057 # Get URLs for the toolchains overlay, if one is to be used.
1058 toolchains_overlay_urls = None
1059 if not options.bootstrap:
1060 toolchains = None
1061 if options.toolchains:
1062 toolchains = options.toolchains.split(',')
1063 elif options.board:
1064 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
1065
1066 if toolchains:
1067 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
1068 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001069
Mike Frysinger80dfce92014-04-21 10:58:53 -04001070 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001071 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001072 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -07001073
Josh Triplett472a4182013-03-08 11:48:57 -08001074 if options.proxy_sim:
1075 _ProxySimSetup(options)
1076
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001077 if (options.delete and not chroot_deleted and
1078 (os.path.exists(options.chroot) or
1079 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001080 lock.write_lock()
1081 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001082
David James56e6c2c2012-10-24 23:54:41 -07001083 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1084 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001085 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001086
David James56e6c2c2012-10-24 23:54:41 -07001087 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001088 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001089 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001090 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001091 continue
1092 lock.write_lock(
1093 "Upgrade to %r needed but chroot is locked; please exit "
1094 "all instances so this upgrade can finish." % src)
1095 if not os.path.exists(src):
1096 # Note that while waiting for the write lock, src may've vanished;
1097 # it's a rare race during the upgrade process that's a byproduct
1098 # of us avoiding taking a write lock to do the src check. If we
1099 # took a write lock for that check, it would effectively limit
1100 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001101 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001102 elif not os.path.exists(target):
1103 # Upgrade occurred, but a reversion, or something whacky
1104 # occurred writing to the old location. Wipe and continue.
1105 os.rename(src, target)
1106 else:
1107 # Upgrade occurred once already, but either a reversion or
1108 # some before/after separate cros_sdk usage is at play.
1109 # Wipe and continue.
1110 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001111
David James56e6c2c2012-10-24 23:54:41 -07001112 if options.download:
1113 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001114 sdk_tarball = FetchRemoteTarballs(
1115 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
1116 if toolchains_overlay_urls:
1117 toolchains_overlay_tarball = FetchRemoteTarballs(
1118 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
1119 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -07001120
David James56e6c2c2012-10-24 23:54:41 -07001121 if options.create:
1122 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001123 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001124 options.cache_dir,
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001125 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001126
David James56e6c2c2012-10-24 23:54:41 -07001127 if options.enter:
1128 lock.read_lock()
1129 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001130 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001131 options.goma_dir, options.goma_client_json,
Don Garrett230d1b22015-03-09 16:21:19 -07001132 chroot_command)