blob: b2492a729d328fed890c1d06df6d307fb9df8842 [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
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070074# As space is used inside the chroot, the empty space in chroot.img is
75# allocated. Deleting files inside the chroot doesn't automatically return the
76# used space to the OS. Over time, this tends to make the sparse chroot.img
77# less sparse even if the chroot contents don't currently need much space. We
78# can recover most of this unused space with fstrim, but that takes too much
79# time to run it every time. Instead, check the used space against the image
80# size after mounting the chroot and only call fstrim if it looks like we could
81# recover at least this many GiB.
82MAX_UNUSED_IMAGE_GBS = 20
83
Mike Frysingercc838832014-05-24 13:10:30 -040084
Brian Harring1790ac42012-09-23 08:53:33 -070085def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080086 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070087 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050088 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
89 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070090 for compressor in COMPRESSION_PREFERENCE]
91
92
93def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050094 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070095 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080096
97
Gilad Arnold6a8f0452015-06-04 11:25:18 -070098def GetToolchainsOverlayUrls(version, toolchains):
99 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700100
101 Args:
102 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
103 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700104 overlays are stored (.../2015/05/ in this case).
105 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700106
107 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700108 List of alternative download URLs for an SDK overlay tarball that contains
109 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700110 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700111 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700112 suburl_template = os.path.join(
113 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700114 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
115 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700116 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
117 for ext in COMPRESSION_PREFERENCE]
118
119
120def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500121 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200122
123 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500124 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200125 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700126 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700127 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200128
129 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700130 Full path to the downloaded file, or None if |allow_none| and no URL worked.
131
132 Raises:
133 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200134 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200135
Brian Harring1790ac42012-09-23 08:53:33 -0700136 # Note we track content length ourselves since certain versions of curl
137 # fail if asked to resume a complete file.
138 # pylint: disable=C0301,W0631
139 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700140 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700141 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200142 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700143 # http://www.logilab.org/ticket/8766
144 # pylint: disable=E1101
145 parsed = urlparse.urlparse(url)
146 tarball_name = os.path.basename(parsed.path)
147 if parsed.scheme in ('', 'file'):
148 if os.path.exists(parsed.path):
149 return parsed.path
150 continue
151 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700152 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800153 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900154 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
155 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700156 successful = False
157 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700158 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700159 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700160 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700161 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500162 elif header.lower().startswith('content-length:'):
163 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700164 if successful:
165 break
166 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200167 break
168 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700169 if allow_none:
170 return None
171 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200172
Brian Harringae0a5322012-09-15 01:46:51 -0700173 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700174 current_size = 0
175 if os.path.exists(tarball_dest):
176 current_size = os.path.getsize(tarball_dest)
177 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700178 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700179 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100180
Brian Harring1790ac42012-09-23 08:53:33 -0700181 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800182 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900183 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
184 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800185
Brian Harring1790ac42012-09-23 08:53:33 -0700186 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700187 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
188 # because we might have partial overlap between known prefixes.
189 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
190 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
191 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700192 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700193 if (filename == tarball_name or
194 any([(filename.startswith(p) and
195 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
196 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700197 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700198 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700199 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200200
Brian Harringb938c782012-02-29 15:14:38 -0800201 return tarball_dest
202
203
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700204def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600205 cache_dir, nousepkg=False):
206 """Creates a new chroot from a given SDK.
207
208 Args:
209 chroot_path: Path where the new chroot will be created.
210 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
211 toolchains_overlay_tarball: Optional path to a second tarball that will be
212 unpacked into the chroot on top of the SDK tarball.
213 cache_dir: Path to a directory that will be used for caching portage files,
214 etc.
215 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
216 chroot.
217 """
Brian Harringb938c782012-02-29 15:14:38 -0800218
Brian Harring1790ac42012-09-23 08:53:33 -0700219 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700220 '--chroot', chroot_path,
221 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700222
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700223 if toolchains_overlay_tarball:
224 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700225
Mike Frysinger2de7f042012-07-10 04:45:03 -0400226 if nousepkg:
227 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800228
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700229 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800230 try:
231 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
236def DeleteChroot(chroot_path):
237 """Deletes an existing chroot"""
238 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
239 '--delete']
240 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700241 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800242 cros_build_lib.RunCommand(cmd, print_cmd=False)
243 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700244 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800245
246
Brian Harringae0a5322012-09-15 01:46:51 -0700247def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900248 workspace, goma_dir, goma_client_json, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800249 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400250 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
251 # The os.ST_NOSUID constant wasn't added until python-3.2.
252 if st.f_flag & 0x2:
253 cros_build_lib.Die('chroot cannot be in a nosuid mount')
254
Brian Harringae0a5322012-09-15 01:46:51 -0700255 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800256 if chrome_root:
257 cmd.extend(['--chrome_root', chrome_root])
258 if chrome_root_mount:
259 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700260 if workspace:
261 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900262 if goma_dir:
263 cmd.extend(['--goma_dir', goma_dir])
264 if goma_client_json:
265 cmd.extend(['--goma_client_json', goma_client_json])
Don Garrett230d1b22015-03-09 16:21:19 -0700266
Brian Harringb938c782012-02-29 15:14:38 -0800267 if len(additional_args) > 0:
268 cmd.append('--')
269 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700270
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700271 # ThinLTO opens lots of files at the same time.
272 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700273 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
274 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700275 # If we were in interactive mode, ignore the exit code; it'll be whatever
276 # they last ran w/in the chroot and won't matter to us one way or another.
277 # Note this does allow chroot entrance to fail and be ignored during
278 # interactive; this is however a rare case and the user will immediately
279 # see it (nor will they be checking the exit code manually).
280 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700281 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800282
283
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600284def _ImageFileForChroot(chroot):
285 """Find the image file that should be associated with |chroot|.
286
287 This function does not check if the image exists; it simply returns the
288 filename that would be used.
289
290 Args:
291 chroot: Path to the chroot.
292
293 Returns:
294 Path to an image file that would be associated with chroot.
295 """
296 return chroot.rstrip('/') + '.img'
297
298
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600299def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
300 """Create a snapshot for the specified chroot VG/LV.
301
302 Args:
303 snapshot_name: The name of the new snapshot.
304 chroot_vg: The name of the VG containing the origin LV.
305 chroot_lv: The name of the origin LV.
306
307 Returns:
308 True if the snapshot was created, or False if a snapshot with the same
309 name already exists.
310
311 Raises:
312 SystemExit: The lvcreate command failed.
313 """
314 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
315 logging.error('Cannot create snapshot %s: A volume with that name already '
316 'exists.', snapshot_name)
317 return False
318
319 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
320 chroot_vg, chroot_lv)]
321 try:
322 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
323 chroot_lv, chroot_vg)
324 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
325 return True
326 except cros_build_lib.RunCommandError:
327 raise SystemExit('Running %r failed!' % cmd)
328
329
330def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
331 """Delete the named snapshot from the specified chroot VG.
332
333 If the requested snapshot is not found, nothing happens. The main chroot LV
334 and internal thinpool LV cannot be deleted with this function.
335
336 Args:
337 snapshot_name: The name of the snapshot to delete.
338 chroot_vg: The name of the VG containing the origin LV.
339 chroot_lv: The name of the origin LV.
340
341 Raises:
342 SystemExit: The lvremove command failed.
343 """
344 if snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
345 cros_build_lib.CHROOT_THINPOOL_NAME):
346 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
347 'if you want to remove the whole chroot.', snapshot_name)
348 return
349
350 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
351 return
352
353 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
354 try:
355 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
356 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
357 except cros_build_lib.RunCommandError:
358 raise SystemExit('Running %r failed!' % cmd)
359
360
361def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
362 """Restore the chroot to an existing snapshot.
363
364 This is done by renaming the original |chroot_lv| LV to a temporary name,
365 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
366 now unused LV. If an error occurs, attempts to rename the original snapshot
367 back to |chroot_lv| to leave the chroot unchanged.
368
369 The chroot must be unmounted before calling this function, and will be left
370 unmounted after this function returns.
371
372 Args:
373 snapshot_name: The name of the snapshot to restore. This snapshot will no
374 longer be accessible at its original name after this function finishes.
375 chroot_vg: The VG containing the chroot LV and snapshot LV.
376 chroot_lv: The name of the original chroot LV.
377
378 Returns:
379 True if the chroot was restored to the requested snapshot, or False if
380 the snapshot wasn't found or isn't valid.
381
382 Raises:
383 SystemExit: Any of the LVM commands failed.
384 """
385 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
386 if (snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
387 cros_build_lib.CHROOT_THINPOOL_NAME) or
388 snapshot_name not in valid_snapshots):
389 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
390 snapshot_name, ', '.join(valid_snapshots))
391 return False
392
393 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
394 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
395 try:
396 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
397 except cros_build_lib.RunCommandError:
398 raise SystemExit('Running %r failed!' % cmd)
399
400 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
401 try:
402 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
403 except cros_build_lib.RunCommandError:
404 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
405 try:
406 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
407 except cros_build_lib.RunCommandError:
408 raise SystemExit('Failed to rename %s to chroot and failed to restore '
409 '%s back to chroot. Failed command: %r' %
410 (snapshot_name, backup_chroot_name, cmd))
411 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
412 'been restored. Failed command: %r' %
413 (snapshot_name, cmd))
414
415 # Some versions of LVM set snapshots to be skipped at auto-activate time.
416 # Other versions don't have this flag at all. We run lvchange to try
417 # disabling auto-skip and activating the volume, but ignore errors. Versions
418 # that don't have the flag should be auto-activated.
419 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
420 cmd = ['lvchange', '-kn', chroot_lv_path]
421 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
422 error_code_ok=True)
423
424 # Activate the LV in case the lvchange above was needed. Activating an LV
425 # that is already active shouldn't do anything, so this is safe to run even if
426 # the -kn wasn't needed.
427 cmd = ['lvchange', '-ay', chroot_lv_path]
428 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
429
430 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
431 try:
432 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
433 except cros_build_lib.RunCommandError:
434 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
435 (chroot_vg, backup_chroot_name, cmd))
436
437 return True
438
439
440def ListChrootSnapshots(chroot_vg, chroot_lv):
441 """Return all snapshots in |chroot_vg| regardless of origin volume.
442
443 Args:
444 chroot_vg: The name of the VG containing the chroot.
445 chroot_lv: The name of the chroot LV.
446
447 Returns:
448 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
449
450 Raises:
451 SystemExit: The lvs command failed.
452 """
453 if not chroot_vg or not chroot_lv:
454 return []
455
456 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
457 '--noheadings', '--separator', '\t', chroot_vg]
458 try:
459 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
460 redirect_stdout=True)
461 except cros_build_lib.RunCommandError:
462 raise SystemExit('Running %r failed!' % cmd)
463
464 # Once the thin origin volume has been deleted, there's no way to tell a
465 # snapshot apart from any other volume. Since this VG is created and managed
466 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
467 # valid snapshots.
468 snapshots = []
469 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
470 for line in result.output.splitlines():
471 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
472 if (lv_name == chroot_lv or
473 lv_name == cros_build_lib.CHROOT_THINPOOL_NAME or
474 pool_lv != cros_build_lib.CHROOT_THINPOOL_NAME or
475 not snapshot_attrs.match(lv_attr)):
476 continue
477 snapshots.append(lv_name)
478 return snapshots
479
480
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600481def _FindSubmounts(*args):
482 """Find all mounts matching each of the paths in |args| and any submounts.
483
484 Returns:
485 A list of all matching mounts in the order found in /proc/mounts.
486 """
487 mounts = []
488 paths = [p.rstrip('/') for p in args]
489 for mtab in osutils.IterateMountPoints():
490 for path in paths:
491 if mtab.destination == path or mtab.destination.startswith(path + '/'):
492 mounts.append(mtab.destination)
493 break
494
495 return mounts
496
497
David James56e6c2c2012-10-24 23:54:41 -0700498def _SudoCommand():
499 """Get the 'sudo' command, along with all needed environment variables."""
500
David James5a73b4d2013-03-07 10:23:40 -0800501 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
502 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700503 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800504 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700505 value = os.environ.get(key)
506 if value is not None:
507 cmd += ['%s=%s' % (key, value)]
508
509 # Pass in the path to the depot_tools so that users can access them from
510 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400511 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500512
David James56e6c2c2012-10-24 23:54:41 -0700513 return cmd
514
515
Josh Triplett472a4182013-03-08 11:48:57 -0800516def _ReportMissing(missing):
517 """Report missing utilities, then exit.
518
519 Args:
520 missing: List of missing utilities, as returned by
521 osutils.FindMissingBinaries. If non-empty, will not return.
522 """
523
524 if missing:
525 raise SystemExit(
526 'The tool(s) %s were not found.\n'
527 'Please install the appropriate package in your host.\n'
528 'Example(ubuntu):\n'
529 ' sudo apt-get install <packagename>'
530 % ', '.join(missing))
531
532
533def _ProxySimSetup(options):
534 """Set up proxy simulator, and return only in the child environment.
535
536 TODO: Ideally, this should support multiple concurrent invocations of
537 cros_sdk --proxy-sim; currently, such invocations will conflict with each
538 other due to the veth device names and IP addresses. Either this code would
539 need to generate fresh, unused names for all of these before forking, or it
540 would need to support multiple concurrent cros_sdk invocations sharing one
541 proxy and allowing it to exit when unused (without counting on any local
542 service-management infrastructure on the host).
543 """
544
545 may_need_mpm = False
546 apache_bin = osutils.Which('apache2')
547 if apache_bin is None:
548 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
549 if apache_bin is None:
550 _ReportMissing(('apache2',))
551 else:
552 may_need_mpm = True
553
554 # Module names and .so names included for ease of grepping.
555 apache_modules = [('proxy_module', 'mod_proxy.so'),
556 ('proxy_connect_module', 'mod_proxy_connect.so'),
557 ('proxy_http_module', 'mod_proxy_http.so'),
558 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
559
560 # Find the apache module directory, and make sure it has the modules we need.
561 module_dirs = {}
562 for g in PROXY_APACHE_MODULE_GLOBS:
563 for mod, so in apache_modules:
564 for f in glob.glob(os.path.join(g, so)):
565 module_dirs.setdefault(os.path.dirname(f), []).append(so)
566 for apache_module_path, modules_found in module_dirs.iteritems():
567 if len(modules_found) == len(apache_modules):
568 break
569 else:
570 # Appease cros lint, which doesn't understand that this else block will not
571 # fall through to the subsequent code which relies on apache_module_path.
572 apache_module_path = None
573 raise SystemExit(
574 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500575 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800576
577 def check_add_module(name):
578 so = 'mod_%s.so' % name
579 if os.access(os.path.join(apache_module_path, so), os.F_OK):
580 mod = '%s_module' % name
581 apache_modules.append((mod, so))
582 return True
583 return False
584
585 check_add_module('authz_core')
586 if may_need_mpm:
587 for mpm in PROXY_APACHE_MPMS:
588 if check_add_module('mpm_%s' % mpm):
589 break
590
591 veth_host = '%s-host' % PROXY_VETH_PREFIX
592 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
593
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500594 # Set up locks to sync the net namespace setup. We need the child to create
595 # the net ns first, and then have the parent assign the guest end of the veth
596 # interface to the child's new network namespace & bring up the proxy. Only
597 # then can the child move forward and rely on the network being up.
598 ns_create_lock = locking.PipeLock()
599 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800600
601 pid = os.fork()
602 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500603 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800604 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500605
606 # Signal the parent the ns is ready to be configured.
607 ns_create_lock.Post()
608 del ns_create_lock
609
610 # Wait for the parent to finish setting up the ns/proxy.
611 ns_setup_lock.Wait()
612 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800613
614 # Set up child side of the network.
615 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500616 ('ip', 'link', 'set', 'up', 'lo'),
617 ('ip', 'address', 'add',
618 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
619 'dev', veth_guest),
620 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800621 )
622 try:
623 for cmd in commands:
624 cros_build_lib.RunCommand(cmd, print_cmd=False)
625 except cros_build_lib.RunCommandError:
626 raise SystemExit('Running %r failed!' % (cmd,))
627
628 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
629 for proto in ('http', 'https', 'ftp'):
630 os.environ[proto + '_proxy'] = proxy_url
631 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
632 os.environ.pop(v, None)
633 return
634
Josh Triplett472a4182013-03-08 11:48:57 -0800635 # Set up parent side of the network.
636 uid = int(os.environ.get('SUDO_UID', '0'))
637 gid = int(os.environ.get('SUDO_GID', '0'))
638 if uid == 0 or gid == 0:
639 for username in PROXY_APACHE_FALLBACK_USERS:
640 try:
641 pwnam = pwd.getpwnam(username)
642 uid, gid = pwnam.pw_uid, pwnam.pw_gid
643 break
644 except KeyError:
645 continue
646 if uid == 0 or gid == 0:
647 raise SystemExit('Could not find a non-root user to run Apache as')
648
649 chroot_parent, chroot_base = os.path.split(options.chroot)
650 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
651 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
652
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500653 # Wait for the child to create the net ns.
654 ns_create_lock.Wait()
655 del ns_create_lock
656
Josh Triplett472a4182013-03-08 11:48:57 -0800657 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500658 'User #%u' % uid,
659 'Group #%u' % gid,
660 'PidFile %s' % pid_file,
661 'ErrorLog %s' % log_file,
662 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
663 'ServerName %s' % PROXY_HOST_IP,
664 'ProxyRequests On',
665 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800666 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500667 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
668 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800669 ]
670 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500671 ('ip', 'link', 'add', 'name', veth_host,
672 'type', 'veth', 'peer', 'name', veth_guest),
673 ('ip', 'address', 'add',
674 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
675 'dev', veth_host),
676 ('ip', 'link', 'set', veth_host, 'up'),
677 ([apache_bin, '-f', '/dev/null'] +
678 [arg for d in apache_directives for arg in ('-C', d)]),
679 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800680 )
681 cmd = None # Make cros lint happy.
682 try:
683 for cmd in commands:
684 cros_build_lib.RunCommand(cmd, print_cmd=False)
685 except cros_build_lib.RunCommandError:
686 # Clean up existing interfaces, if any.
687 cmd_cleanup = ('ip', 'link', 'del', veth_host)
688 try:
689 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
690 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700691 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800692 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500693
694 # Signal the child that the net ns/proxy is fully configured now.
695 ns_setup_lock.Post()
696 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800697
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400698 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800699
700
Mike Frysingera78a56e2012-11-20 06:02:30 -0500701def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700702 """Re-execute cros_sdk as root.
703
704 Also unshare the mount namespace so as to ensure that processes outside
705 the chroot can't mess with our mounts.
706 """
707 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500708 cmd = _SudoCommand() + ['--'] + argv
709 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500710 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400711 # We must set up the cgroups mounts before we enter our own namespace.
712 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800713 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700714
715
Mike Frysinger34db8692013-11-11 14:54:08 -0500716def _CreateParser(sdk_latest_version, bootstrap_latest_version):
717 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400718 usage = ('usage: %(prog)s [options] '
719 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
720 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
721 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700722
Mike Frysinger34db8692013-11-11 14:54:08 -0500723 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500724 default_chroot = os.path.join(constants.SOURCE_ROOT,
725 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400726 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700727 '--chroot', dest='chroot', default=default_chroot, type='path',
728 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600729 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
730 default=True,
731 help='Do not mount the chroot on a loopback image; '
732 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800733
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400734 parser.add_argument('--chrome_root', type='path',
735 help='Mount this chrome root into the SDK chroot')
736 parser.add_argument('--chrome_root_mount', type='path',
737 help='Mount chrome into this path inside SDK chroot')
738 parser.add_argument('--nousepkg', action='store_true', default=False,
739 help='Do not use binary packages when creating a chroot.')
740 parser.add_argument('-u', '--url', dest='sdk_url',
741 help='Use sdk tarball located at this url. Use file:// '
742 'for local files.')
743 parser.add_argument('--sdk-version',
744 help=('Use this sdk version. For prebuilt, current is %r'
745 ', for bootstrapping it is %r.'
746 % (sdk_latest_version, bootstrap_latest_version)))
747 parser.add_argument('--workspace',
748 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900749 parser.add_argument('--goma_dir', type='path',
750 help='Goma installed directory to mount into the chroot.')
751 parser.add_argument('--goma_client_json', type='path',
752 help='Service account json file to use goma on bot. '
753 'Mounted into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400754 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500755
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700756 # SDK overlay tarball options (mutually exclusive).
757 group = parser.add_mutually_exclusive_group()
758 group.add_argument('--toolchains',
759 help=('Comma-separated list of toolchains we expect to be '
760 'using on the chroot. Used for downloading a '
761 'corresponding SDK toolchains group (if one is '
762 'found), which may speed up chroot initialization '
763 'when building for the first time. Otherwise this '
764 'has no effect and will not restrict the chroot in '
765 'any way. Ignored if using --bootstrap.'))
766 group.add_argument('--board',
767 help=('The board we intend to be building in the chroot. '
768 'Used for deriving the list of required toolchains '
769 '(see --toolchains).'))
770
Mike Frysinger34db8692013-11-11 14:54:08 -0500771 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400772 group = parser.add_argument_group('Commands')
773 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500774 '--enter', action='store_true', default=False,
775 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400776 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500777 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500778 help='Create the chroot only if it does not already exist. '
779 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400780 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500781 '--bootstrap', action='store_true', default=False,
782 help='Build everything from scratch, including the sdk. '
783 'Use this only if you need to validate a change '
784 'that affects SDK creation itself (toolchain and '
785 'build are typically the only folk who need this). '
786 'Note this will quite heavily slow down the build. '
787 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400788 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500789 '-r', '--replace', action='store_true', default=False,
790 help='Replace an existing SDK chroot. Basically an alias '
791 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400792 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500793 '--delete', action='store_true', default=False,
794 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400795 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500796 '--download', action='store_true', default=False,
797 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600798 group.add_argument(
799 '--snapshot-create', metavar='SNAPSHOT_NAME',
800 help='Create a snapshot of the chroot. Requires that the chroot was '
801 'created without the --nouse-image option.')
802 group.add_argument(
803 '--snapshot-restore', metavar='SNAPSHOT_NAME',
804 help='Restore the chroot to a previously created snapshot.')
805 group.add_argument(
806 '--snapshot-delete', metavar='SNAPSHOT_NAME',
807 help='Delete a previously created snapshot. Deleting a snapshot that '
808 'does not exist is not an error.')
809 group.add_argument(
810 '--snapshot-list', action='store_true', default=False,
811 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500812 commands = group
813
Mike Frysinger80dfce92014-04-21 10:58:53 -0400814 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400815 group = parser.add_argument_group('Namespaces')
816 group.add_argument('--proxy-sim', action='store_true', default=False,
817 help='Simulate a restrictive network requiring an outbound'
818 ' proxy.')
819 group.add_argument('--no-ns-pid', dest='ns_pid',
820 default=True, action='store_false',
821 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400822
Mike Frysinger34db8692013-11-11 14:54:08 -0500823 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400824 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500825 'Internal Chromium OS Build Team Options',
826 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400827 group.add_argument('--buildbot-log-version', default=False,
828 action='store_true',
829 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500830
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400831 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500832
833
834def main(argv):
835 conf = cros_build_lib.LoadKeyValueFile(
836 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
837 ignore_missing=True)
838 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
839 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
840 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400841 options = parser.parse_args(argv)
842 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800843
844 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500845 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800846
Brian Harring1790ac42012-09-23 08:53:33 -0700847 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700848 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600849 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700850 "cros_sdk is currently only supported on x86_64; you're running"
851 " %s. Please find a x86_64 machine." % (host,))
852
Josh Triplett472a4182013-03-08 11:48:57 -0800853 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
854 if options.proxy_sim:
855 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600856 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800857
Benjamin Gordon040a1162017-06-29 13:44:47 -0600858 if (sdk_latest_version == '<unknown>' or
859 bootstrap_latest_version == '<unknown>'):
860 cros_build_lib.Die(
861 'No SDK version was found. '
862 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
863 'Please change to a directory inside your Chromium OS source tree\n'
864 'and retry. If you need to setup a Chromium OS source tree, see\n'
865 ' http://www.chromium.org/chromium-os/developer-guide')
866
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600867 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
868 or options.snapshot_delete or options.snapshot_list)
869 if any_snapshot_operation and not options.use_image:
870 cros_build_lib.Die('Snapshot operations are not compatible with '
871 '--nouse-image.')
872
873 if (options.snapshot_delete and options.snapshot_delete ==
874 options.snapshot_restore):
875 parser.error('Cannot --snapshot_delete the same snapshot you are '
876 'restoring with --snapshot_restore.')
877
David James471532c2013-01-21 10:23:31 -0800878 _ReExecuteIfNeeded([sys.argv[0]] + argv)
879
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600880 lock_path = os.path.dirname(options.chroot)
881 lock_path = os.path.join(
882 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
883
Brian Harring218e13c2012-10-10 16:21:26 -0700884 # Expand out the aliases...
885 if options.replace:
886 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800887
Brian Harring218e13c2012-10-10 16:21:26 -0700888 if options.bootstrap:
889 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800890
Brian Harring218e13c2012-10-10 16:21:26 -0700891 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400892 # pylint: disable=protected-access
893 # This _group_actions access sucks, but upstream decided to not include an
894 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700895 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400896 for x in commands._group_actions)
897 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700898 options.enter |= bool(chroot_command)
899
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600900 if (options.delete and not options.create and
901 (options.enter or any_snapshot_operation)):
902 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700903 "was specified makes no sense.")
904
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600905 # Clean up potential leftovers from previous interrupted builds.
906 # TODO(bmgordon): Remove this at the end of 2017. That should be long enough
907 # to get rid of them all.
908 chroot_build_path = options.chroot + '.build'
909 if options.use_image and os.path.exists(chroot_build_path):
910 try:
911 with cgroups.SimpleContainChildren('cros_sdk'):
912 with locking.FileLock(lock_path, 'chroot lock') as lock:
913 logging.notice('Cleaning up leftover build directory %s',
914 chroot_build_path)
915 lock.write_lock()
916 osutils.UmountTree(chroot_build_path)
917 osutils.RmDir(chroot_build_path)
918 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600919 logging.warning('Unable to remove %s: %s', chroot_build_path, e)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600920
Benjamin Gordon35194f12017-07-19 10:26:22 -0600921 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600922 chroot_ver_file = os.path.join(options.chroot, 'etc', 'cros_chroot_version')
923 chroot_exists = os.path.exists(chroot_ver_file)
924 if (options.use_image and not chroot_exists and not options.delete and
925 not missing_image_tools and
926 os.path.exists(_ImageFileForChroot(options.chroot))):
927 # Try to re-mount an existing image in case the user has rebooted.
928 with cgroups.SimpleContainChildren('cros_sdk'):
929 with locking.FileLock(lock_path, 'chroot lock') as lock:
930 logging.debug('Checking if existing chroot image can be mounted.')
931 lock.write_lock()
932 cros_build_lib.MountChroot(options.chroot, create=False)
933 chroot_exists = os.path.exists(chroot_ver_file)
934 if chroot_exists:
935 logging.notice('Mounted existing image %s on chroot',
936 _ImageFileForChroot(options.chroot))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600937 if (options.create or options.enter or options.snapshot_create or
938 options.snapshot_restore):
Brian Harring218e13c2012-10-10 16:21:26 -0700939 # Only create if it's being wiped, or if it doesn't exist.
940 if not options.delete and chroot_exists:
941 options.create = False
942 else:
943 options.download = True
944
945 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600946 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700947 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800948
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600949 # Anything that needs to manipulate the main chroot mount or communicate with
950 # LVM needs to be done here before we enter the new namespaces.
951
952 # If deleting, do it regardless of the use_image flag so that a
953 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600954 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
955 # favor of this block.
956 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600957 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600958 with cgroups.SimpleContainChildren('cros_sdk'):
959 with locking.FileLock(lock_path, 'chroot lock') as lock:
960 lock.write_lock()
961 if missing_image_tools:
962 logging.notice('Unmounting chroot.')
963 osutils.UmountTree(options.chroot)
964 else:
965 logging.notice('Deleting chroot.')
966 cros_build_lib.CleanupChrootMount(options.chroot, delete_image=True)
967 osutils.RmDir(options.chroot, ignore_missing=True)
968 chroot_deleted = True
969
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600970 # Make sure the main chroot mount is visible. Contents will be filled in
971 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600972 if options.create and options.use_image:
973 if missing_image_tools:
974 raise SystemExit(
975 '''The tool(s) %s were not found.
976Please make sure the lvm2 and thin-provisioning-tools packages
977are installed on your host.
978Example(ubuntu):
979 sudo apt-get install lvm2 thin-provisioning-tools
980
981If you want to run without lvm2, pass --nouse-image (chroot
982snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600983
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600984 logging.debug('Making sure chroot image is mounted.')
985 with cgroups.SimpleContainChildren('cros_sdk'):
986 with locking.FileLock(lock_path, 'chroot lock') as lock:
987 lock.write_lock()
988 if not cros_build_lib.MountChroot(options.chroot, create=True):
989 cros_build_lib.Die('Unable to mount %s on chroot',
990 _ImageFileForChroot(options.chroot))
991 logging.notice('Mounted %s on chroot',
992 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600993
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600994 # Snapshot operations will always need the VG/LV, but other actions won't.
995 if any_snapshot_operation:
996 with cgroups.SimpleContainChildren('cros_sdk'):
997 with locking.FileLock(lock_path, 'chroot lock') as lock:
998 chroot_vg, chroot_lv = cros_build_lib.FindChrootMountSource(
999 options.chroot)
1000 if not chroot_vg or not chroot_lv:
1001 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
1002 options.chroot)
1003
1004 # Delete snapshot before creating a new one. This allows the user to
1005 # throw out old state, create a new snapshot, and enter the chroot in a
1006 # single call to cros_sdk. Since restore involves deleting, also do it
1007 # before creating.
1008 if options.snapshot_restore:
1009 lock.write_lock()
1010 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1011 if options.snapshot_restore not in valid_snapshots:
1012 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
1013 'Valid snapshots: %s', options.snapshot_restore,
1014 ', '.join(valid_snapshots))
1015 osutils.UmountTree(options.chroot)
1016 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1017 chroot_lv):
1018 cros_build_lib.Die('Unable to restore chroot to snapshot.')
1019 if not cros_build_lib.MountChroot(options.chroot, create=False):
1020 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1021
1022 # Use a read lock for snapshot delete and create even though they modify
1023 # the filesystem, because they don't modify the mounted chroot itself.
1024 # The underlying LVM commands take their own locks, so conflicting
1025 # concurrent operations here may crash cros_sdk, but won't corrupt the
1026 # chroot image. This tradeoff seems worth it to allow snapshot
1027 # operations on chroots that have a process inside.
1028 if options.snapshot_delete:
1029 lock.read_lock()
1030 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1031
1032 if options.snapshot_create:
1033 lock.read_lock()
1034 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1035 chroot_lv):
1036 cros_build_lib.Die('Unable to create snapshot.')
1037
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001038 img_path = _ImageFileForChroot(options.chroot)
1039 if (options.use_image and os.path.exists(options.chroot) and
1040 os.path.exists(img_path)):
1041 img_stat = os.stat(img_path)
1042 img_used_bytes = img_stat.st_blocks * 512
1043
1044 mount_stat = os.statvfs(options.chroot)
1045 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1046 mount_stat.f_bfree)
1047
1048 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1049 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1050 logging.notice('%s is using %s GiB more than needed. Running '
1051 'fstrim.', img_path, extra_gbs)
1052 cmd = ['fstrim', options.chroot]
1053 try:
1054 cros_build_lib.RunCommand(cmd, print_cmd=False)
1055 except cros_build_lib.RunCommandError as e:
1056 logging.warning('Running fstrim failed. Consider running fstrim on '
1057 'your chroot manually.\nError: %s', e)
1058
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001059 # Enter a new set of namespaces. Everything after here cannot directly affect
1060 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001061 namespaces.SimpleUnshare()
1062 if options.ns_pid:
1063 first_pid = namespaces.CreatePidNs()
1064 else:
1065 first_pid = None
1066
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001067 if options.snapshot_list:
1068 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1069 print(snap)
1070 sys.exit(0)
1071
Brian Harringb938c782012-02-29 15:14:38 -08001072 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001073 sdk_version = (bootstrap_latest_version if options.bootstrap
1074 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001075 else:
1076 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001077 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001078 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001079
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001080 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -07001081 if options.sdk_url:
1082 urls = [options.sdk_url]
1083 elif options.bootstrap:
1084 urls = GetStage3Urls(sdk_version)
1085 else:
1086 urls = GetArchStageTarballs(sdk_version)
1087
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001088 # Get URLs for the toolchains overlay, if one is to be used.
1089 toolchains_overlay_urls = None
1090 if not options.bootstrap:
1091 toolchains = None
1092 if options.toolchains:
1093 toolchains = options.toolchains.split(',')
1094 elif options.board:
1095 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
1096
1097 if toolchains:
1098 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
1099 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001100
Mike Frysinger80dfce92014-04-21 10:58:53 -04001101 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001102 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001103 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -07001104
Josh Triplett472a4182013-03-08 11:48:57 -08001105 if options.proxy_sim:
1106 _ProxySimSetup(options)
1107
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001108 if (options.delete and not chroot_deleted and
1109 (os.path.exists(options.chroot) or
1110 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001111 lock.write_lock()
1112 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001113
David James56e6c2c2012-10-24 23:54:41 -07001114 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1115 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001116 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001117
David James56e6c2c2012-10-24 23:54:41 -07001118 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001119 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001120 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001121 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001122 continue
1123 lock.write_lock(
1124 "Upgrade to %r needed but chroot is locked; please exit "
1125 "all instances so this upgrade can finish." % src)
1126 if not os.path.exists(src):
1127 # Note that while waiting for the write lock, src may've vanished;
1128 # it's a rare race during the upgrade process that's a byproduct
1129 # of us avoiding taking a write lock to do the src check. If we
1130 # took a write lock for that check, it would effectively limit
1131 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001132 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001133 elif not os.path.exists(target):
1134 # Upgrade occurred, but a reversion, or something whacky
1135 # occurred writing to the old location. Wipe and continue.
1136 os.rename(src, target)
1137 else:
1138 # Upgrade occurred once already, but either a reversion or
1139 # some before/after separate cros_sdk usage is at play.
1140 # Wipe and continue.
1141 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001142
David James56e6c2c2012-10-24 23:54:41 -07001143 if options.download:
1144 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001145 sdk_tarball = FetchRemoteTarballs(
1146 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
1147 if toolchains_overlay_urls:
1148 toolchains_overlay_tarball = FetchRemoteTarballs(
1149 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
1150 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -07001151
David James56e6c2c2012-10-24 23:54:41 -07001152 if options.create:
1153 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001154 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001155 options.cache_dir,
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001156 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001157
David James56e6c2c2012-10-24 23:54:41 -07001158 if options.enter:
1159 lock.read_lock()
1160 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001161 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001162 options.goma_dir, options.goma_client_json,
Don Garrett230d1b22015-03-09 16:21:19 -07001163 chroot_command)