blob: 3bb0fc84a3412b12bb013fcbf88b37796719141b [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
Yong Hong84ba9172018-02-07 01:37:42 +080035from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040036from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080037from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050038from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080039
40cros_build_lib.STRICT_SUDO = True
41
42
Zdenek Behanaa52cea2012-05-30 01:31:11 +020043COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020044
Brian Harringb938c782012-02-29 15:14:38 -080045# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050046MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
47 'src/scripts/sdk_lib/make_chroot.sh')]
48ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
49 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080050
Josh Triplett472a4182013-03-08 11:48:57 -080051# Proxy simulator configuration.
52PROXY_HOST_IP = '192.168.240.1'
53PROXY_PORT = 8080
54PROXY_GUEST_IP = '192.168.240.2'
55PROXY_NETMASK = 30
56PROXY_VETH_PREFIX = 'veth'
57PROXY_CONNECT_PORTS = (80, 443, 9418)
58PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
59PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
60PROXY_APACHE_FALLBACK_PATH = ':'.join(
61 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
62)
63PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
64
Josh Triplett9a495f62013-03-15 18:06:55 -070065# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080066NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080067
Josh Triplett472a4182013-03-08 11:48:57 -080068# Tools needed for --proxy-sim only.
69PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080070
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060071# Tools needed when use_image is true (the default).
72IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060073 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060074
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070075# As space is used inside the chroot, the empty space in chroot.img is
76# allocated. Deleting files inside the chroot doesn't automatically return the
77# used space to the OS. Over time, this tends to make the sparse chroot.img
78# less sparse even if the chroot contents don't currently need much space. We
79# can recover most of this unused space with fstrim, but that takes too much
80# time to run it every time. Instead, check the used space against the image
81# size after mounting the chroot and only call fstrim if it looks like we could
82# recover at least this many GiB.
83MAX_UNUSED_IMAGE_GBS = 20
84
Mike Frysingercc838832014-05-24 13:10:30 -040085
Brian Harring1790ac42012-09-23 08:53:33 -070086def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080087 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070088 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050089 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
90 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070091 for compressor in COMPRESSION_PREFERENCE]
92
93
94def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050095 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070096 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080097
98
Gilad Arnold6a8f0452015-06-04 11:25:18 -070099def GetToolchainsOverlayUrls(version, toolchains):
100 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700101
102 Args:
103 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
104 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700105 overlays are stored (.../2015/05/ in this case).
106 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700107
108 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700109 List of alternative download URLs for an SDK overlay tarball that contains
110 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700111 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700112 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700113 suburl_template = os.path.join(
114 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700115 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
116 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700117 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
118 for ext in COMPRESSION_PREFERENCE]
119
120
121def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500122 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200123
124 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500125 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200126 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700127 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700128 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200129
130 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700131 Full path to the downloaded file, or None if |allow_none| and no URL worked.
132
133 Raises:
134 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200135 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200136
Brian Harring1790ac42012-09-23 08:53:33 -0700137 # Note we track content length ourselves since certain versions of curl
138 # fail if asked to resume a complete file.
139 # pylint: disable=C0301,W0631
140 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700141 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700142 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200143 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700144 # http://www.logilab.org/ticket/8766
145 # pylint: disable=E1101
146 parsed = urlparse.urlparse(url)
147 tarball_name = os.path.basename(parsed.path)
148 if parsed.scheme in ('', 'file'):
149 if os.path.exists(parsed.path):
150 return parsed.path
151 continue
152 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700153 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800154 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900155 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
156 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700157 successful = False
158 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700159 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700160 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700161 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700162 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500163 elif header.lower().startswith('content-length:'):
164 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700165 if successful:
166 break
167 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200168 break
169 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700170 if allow_none:
171 return None
172 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200173
Brian Harringae0a5322012-09-15 01:46:51 -0700174 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700175 current_size = 0
176 if os.path.exists(tarball_dest):
177 current_size = os.path.getsize(tarball_dest)
178 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700179 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700180 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100181
Brian Harring1790ac42012-09-23 08:53:33 -0700182 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800183 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900184 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
185 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800186
Brian Harring1790ac42012-09-23 08:53:33 -0700187 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700188 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
189 # because we might have partial overlap between known prefixes.
190 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
191 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
192 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700193 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700194 if (filename == tarball_name or
195 any([(filename.startswith(p) and
196 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
197 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700198 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700199 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700200 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200201
Brian Harringb938c782012-02-29 15:14:38 -0800202 return tarball_dest
203
204
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700205def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600206 cache_dir, nousepkg=False):
207 """Creates a new chroot from a given SDK.
208
209 Args:
210 chroot_path: Path where the new chroot will be created.
211 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
212 toolchains_overlay_tarball: Optional path to a second tarball that will be
213 unpacked into the chroot on top of the SDK tarball.
214 cache_dir: Path to a directory that will be used for caching portage files,
215 etc.
216 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
217 chroot.
218 """
Brian Harringb938c782012-02-29 15:14:38 -0800219
Brian Harring1790ac42012-09-23 08:53:33 -0700220 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700221 '--chroot', chroot_path,
222 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700223
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700224 if toolchains_overlay_tarball:
225 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700226
Mike Frysinger2de7f042012-07-10 04:45:03 -0400227 if nousepkg:
228 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800229
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700230 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800231 try:
232 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
237def DeleteChroot(chroot_path):
238 """Deletes an existing chroot"""
239 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
240 '--delete']
241 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700242 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800243 cros_build_lib.RunCommand(cmd, print_cmd=False)
244 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700245 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800246
247
Brian Harringae0a5322012-09-15 01:46:51 -0700248def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Yong Hong84ba9172018-02-07 01:37:42 +0800249 workspace, goma_dir, goma_client_json, working_dir,
250 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800251 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400252 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
253 # The os.ST_NOSUID constant wasn't added until python-3.2.
254 if st.f_flag & 0x2:
255 cros_build_lib.Die('chroot cannot be in a nosuid mount')
256
Brian Harringae0a5322012-09-15 01:46:51 -0700257 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800258 if chrome_root:
259 cmd.extend(['--chrome_root', chrome_root])
260 if chrome_root_mount:
261 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700262 if workspace:
263 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900264 if goma_dir:
265 cmd.extend(['--goma_dir', goma_dir])
266 if goma_client_json:
267 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800268 if working_dir is not None:
269 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700270
Brian Harringb938c782012-02-29 15:14:38 -0800271 if len(additional_args) > 0:
272 cmd.append('--')
273 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700274
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700275 # ThinLTO opens lots of files at the same time.
276 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700277 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
278 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700279 # If we were in interactive mode, ignore the exit code; it'll be whatever
280 # they last ran w/in the chroot and won't matter to us one way or another.
281 # Note this does allow chroot entrance to fail and be ignored during
282 # interactive; this is however a rare case and the user will immediately
283 # see it (nor will they be checking the exit code manually).
284 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700285 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800286
287
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600288def _ImageFileForChroot(chroot):
289 """Find the image file that should be associated with |chroot|.
290
291 This function does not check if the image exists; it simply returns the
292 filename that would be used.
293
294 Args:
295 chroot: Path to the chroot.
296
297 Returns:
298 Path to an image file that would be associated with chroot.
299 """
300 return chroot.rstrip('/') + '.img'
301
302
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600303def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
304 """Create a snapshot for the specified chroot VG/LV.
305
306 Args:
307 snapshot_name: The name of the new snapshot.
308 chroot_vg: The name of the VG containing the origin LV.
309 chroot_lv: The name of the origin LV.
310
311 Returns:
312 True if the snapshot was created, or False if a snapshot with the same
313 name already exists.
314
315 Raises:
316 SystemExit: The lvcreate command failed.
317 """
318 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
319 logging.error('Cannot create snapshot %s: A volume with that name already '
320 'exists.', snapshot_name)
321 return False
322
323 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
324 chroot_vg, chroot_lv)]
325 try:
326 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
327 chroot_lv, chroot_vg)
328 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
329 return True
330 except cros_build_lib.RunCommandError:
331 raise SystemExit('Running %r failed!' % cmd)
332
333
334def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
335 """Delete the named snapshot from the specified chroot VG.
336
337 If the requested snapshot is not found, nothing happens. The main chroot LV
338 and internal thinpool LV cannot be deleted with this function.
339
340 Args:
341 snapshot_name: The name of the snapshot to delete.
342 chroot_vg: The name of the VG containing the origin LV.
343 chroot_lv: The name of the origin LV.
344
345 Raises:
346 SystemExit: The lvremove command failed.
347 """
348 if snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
349 cros_build_lib.CHROOT_THINPOOL_NAME):
350 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
351 'if you want to remove the whole chroot.', snapshot_name)
352 return
353
354 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
355 return
356
357 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
358 try:
359 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
360 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
361 except cros_build_lib.RunCommandError:
362 raise SystemExit('Running %r failed!' % cmd)
363
364
365def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
366 """Restore the chroot to an existing snapshot.
367
368 This is done by renaming the original |chroot_lv| LV to a temporary name,
369 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
370 now unused LV. If an error occurs, attempts to rename the original snapshot
371 back to |chroot_lv| to leave the chroot unchanged.
372
373 The chroot must be unmounted before calling this function, and will be left
374 unmounted after this function returns.
375
376 Args:
377 snapshot_name: The name of the snapshot to restore. This snapshot will no
378 longer be accessible at its original name after this function finishes.
379 chroot_vg: The VG containing the chroot LV and snapshot LV.
380 chroot_lv: The name of the original chroot LV.
381
382 Returns:
383 True if the chroot was restored to the requested snapshot, or False if
384 the snapshot wasn't found or isn't valid.
385
386 Raises:
387 SystemExit: Any of the LVM commands failed.
388 """
389 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
390 if (snapshot_name in (cros_build_lib.CHROOT_LV_NAME,
391 cros_build_lib.CHROOT_THINPOOL_NAME) or
392 snapshot_name not in valid_snapshots):
393 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
394 snapshot_name, ', '.join(valid_snapshots))
395 return False
396
397 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
398 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
399 try:
400 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
401 except cros_build_lib.RunCommandError:
402 raise SystemExit('Running %r failed!' % cmd)
403
404 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
405 try:
406 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
407 except cros_build_lib.RunCommandError:
408 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
409 try:
410 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
411 except cros_build_lib.RunCommandError:
412 raise SystemExit('Failed to rename %s to chroot and failed to restore '
413 '%s back to chroot. Failed command: %r' %
414 (snapshot_name, backup_chroot_name, cmd))
415 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
416 'been restored. Failed command: %r' %
417 (snapshot_name, cmd))
418
419 # Some versions of LVM set snapshots to be skipped at auto-activate time.
420 # Other versions don't have this flag at all. We run lvchange to try
421 # disabling auto-skip and activating the volume, but ignore errors. Versions
422 # that don't have the flag should be auto-activated.
423 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
424 cmd = ['lvchange', '-kn', chroot_lv_path]
425 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
426 error_code_ok=True)
427
428 # Activate the LV in case the lvchange above was needed. Activating an LV
429 # that is already active shouldn't do anything, so this is safe to run even if
430 # the -kn wasn't needed.
431 cmd = ['lvchange', '-ay', chroot_lv_path]
432 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
433
434 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
435 try:
436 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
437 except cros_build_lib.RunCommandError:
438 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
439 (chroot_vg, backup_chroot_name, cmd))
440
441 return True
442
443
444def ListChrootSnapshots(chroot_vg, chroot_lv):
445 """Return all snapshots in |chroot_vg| regardless of origin volume.
446
447 Args:
448 chroot_vg: The name of the VG containing the chroot.
449 chroot_lv: The name of the chroot LV.
450
451 Returns:
452 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
453
454 Raises:
455 SystemExit: The lvs command failed.
456 """
457 if not chroot_vg or not chroot_lv:
458 return []
459
460 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
461 '--noheadings', '--separator', '\t', chroot_vg]
462 try:
463 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
464 redirect_stdout=True)
465 except cros_build_lib.RunCommandError:
466 raise SystemExit('Running %r failed!' % cmd)
467
468 # Once the thin origin volume has been deleted, there's no way to tell a
469 # snapshot apart from any other volume. Since this VG is created and managed
470 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
471 # valid snapshots.
472 snapshots = []
473 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
474 for line in result.output.splitlines():
475 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
476 if (lv_name == chroot_lv or
477 lv_name == cros_build_lib.CHROOT_THINPOOL_NAME or
478 pool_lv != cros_build_lib.CHROOT_THINPOOL_NAME or
479 not snapshot_attrs.match(lv_attr)):
480 continue
481 snapshots.append(lv_name)
482 return snapshots
483
484
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600485def _FindSubmounts(*args):
486 """Find all mounts matching each of the paths in |args| and any submounts.
487
488 Returns:
489 A list of all matching mounts in the order found in /proc/mounts.
490 """
491 mounts = []
492 paths = [p.rstrip('/') for p in args]
493 for mtab in osutils.IterateMountPoints():
494 for path in paths:
495 if mtab.destination == path or mtab.destination.startswith(path + '/'):
496 mounts.append(mtab.destination)
497 break
498
499 return mounts
500
501
David James56e6c2c2012-10-24 23:54:41 -0700502def _SudoCommand():
503 """Get the 'sudo' command, along with all needed environment variables."""
504
David James5a73b4d2013-03-07 10:23:40 -0800505 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
506 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700507 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800508 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700509 value = os.environ.get(key)
510 if value is not None:
511 cmd += ['%s=%s' % (key, value)]
512
513 # Pass in the path to the depot_tools so that users can access them from
514 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400515 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500516
David James56e6c2c2012-10-24 23:54:41 -0700517 return cmd
518
519
Josh Triplett472a4182013-03-08 11:48:57 -0800520def _ReportMissing(missing):
521 """Report missing utilities, then exit.
522
523 Args:
524 missing: List of missing utilities, as returned by
525 osutils.FindMissingBinaries. If non-empty, will not return.
526 """
527
528 if missing:
529 raise SystemExit(
530 'The tool(s) %s were not found.\n'
531 'Please install the appropriate package in your host.\n'
532 'Example(ubuntu):\n'
533 ' sudo apt-get install <packagename>'
534 % ', '.join(missing))
535
536
537def _ProxySimSetup(options):
538 """Set up proxy simulator, and return only in the child environment.
539
540 TODO: Ideally, this should support multiple concurrent invocations of
541 cros_sdk --proxy-sim; currently, such invocations will conflict with each
542 other due to the veth device names and IP addresses. Either this code would
543 need to generate fresh, unused names for all of these before forking, or it
544 would need to support multiple concurrent cros_sdk invocations sharing one
545 proxy and allowing it to exit when unused (without counting on any local
546 service-management infrastructure on the host).
547 """
548
549 may_need_mpm = False
550 apache_bin = osutils.Which('apache2')
551 if apache_bin is None:
552 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
553 if apache_bin is None:
554 _ReportMissing(('apache2',))
555 else:
556 may_need_mpm = True
557
558 # Module names and .so names included for ease of grepping.
559 apache_modules = [('proxy_module', 'mod_proxy.so'),
560 ('proxy_connect_module', 'mod_proxy_connect.so'),
561 ('proxy_http_module', 'mod_proxy_http.so'),
562 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
563
564 # Find the apache module directory, and make sure it has the modules we need.
565 module_dirs = {}
566 for g in PROXY_APACHE_MODULE_GLOBS:
567 for mod, so in apache_modules:
568 for f in glob.glob(os.path.join(g, so)):
569 module_dirs.setdefault(os.path.dirname(f), []).append(so)
570 for apache_module_path, modules_found in module_dirs.iteritems():
571 if len(modules_found) == len(apache_modules):
572 break
573 else:
574 # Appease cros lint, which doesn't understand that this else block will not
575 # fall through to the subsequent code which relies on apache_module_path.
576 apache_module_path = None
577 raise SystemExit(
578 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500579 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800580
581 def check_add_module(name):
582 so = 'mod_%s.so' % name
583 if os.access(os.path.join(apache_module_path, so), os.F_OK):
584 mod = '%s_module' % name
585 apache_modules.append((mod, so))
586 return True
587 return False
588
589 check_add_module('authz_core')
590 if may_need_mpm:
591 for mpm in PROXY_APACHE_MPMS:
592 if check_add_module('mpm_%s' % mpm):
593 break
594
595 veth_host = '%s-host' % PROXY_VETH_PREFIX
596 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
597
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500598 # Set up locks to sync the net namespace setup. We need the child to create
599 # the net ns first, and then have the parent assign the guest end of the veth
600 # interface to the child's new network namespace & bring up the proxy. Only
601 # then can the child move forward and rely on the network being up.
602 ns_create_lock = locking.PipeLock()
603 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800604
605 pid = os.fork()
606 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500607 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800608 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500609
610 # Signal the parent the ns is ready to be configured.
611 ns_create_lock.Post()
612 del ns_create_lock
613
614 # Wait for the parent to finish setting up the ns/proxy.
615 ns_setup_lock.Wait()
616 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800617
618 # Set up child side of the network.
619 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500620 ('ip', 'link', 'set', 'up', 'lo'),
621 ('ip', 'address', 'add',
622 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
623 'dev', veth_guest),
624 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800625 )
626 try:
627 for cmd in commands:
628 cros_build_lib.RunCommand(cmd, print_cmd=False)
629 except cros_build_lib.RunCommandError:
630 raise SystemExit('Running %r failed!' % (cmd,))
631
632 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
633 for proto in ('http', 'https', 'ftp'):
634 os.environ[proto + '_proxy'] = proxy_url
635 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
636 os.environ.pop(v, None)
637 return
638
Josh Triplett472a4182013-03-08 11:48:57 -0800639 # Set up parent side of the network.
640 uid = int(os.environ.get('SUDO_UID', '0'))
641 gid = int(os.environ.get('SUDO_GID', '0'))
642 if uid == 0 or gid == 0:
643 for username in PROXY_APACHE_FALLBACK_USERS:
644 try:
645 pwnam = pwd.getpwnam(username)
646 uid, gid = pwnam.pw_uid, pwnam.pw_gid
647 break
648 except KeyError:
649 continue
650 if uid == 0 or gid == 0:
651 raise SystemExit('Could not find a non-root user to run Apache as')
652
653 chroot_parent, chroot_base = os.path.split(options.chroot)
654 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
655 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
656
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500657 # Wait for the child to create the net ns.
658 ns_create_lock.Wait()
659 del ns_create_lock
660
Josh Triplett472a4182013-03-08 11:48:57 -0800661 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500662 'User #%u' % uid,
663 'Group #%u' % gid,
664 'PidFile %s' % pid_file,
665 'ErrorLog %s' % log_file,
666 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
667 'ServerName %s' % PROXY_HOST_IP,
668 'ProxyRequests On',
669 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800670 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500671 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
672 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800673 ]
674 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500675 ('ip', 'link', 'add', 'name', veth_host,
676 'type', 'veth', 'peer', 'name', veth_guest),
677 ('ip', 'address', 'add',
678 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
679 'dev', veth_host),
680 ('ip', 'link', 'set', veth_host, 'up'),
681 ([apache_bin, '-f', '/dev/null'] +
682 [arg for d in apache_directives for arg in ('-C', d)]),
683 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800684 )
685 cmd = None # Make cros lint happy.
686 try:
687 for cmd in commands:
688 cros_build_lib.RunCommand(cmd, print_cmd=False)
689 except cros_build_lib.RunCommandError:
690 # Clean up existing interfaces, if any.
691 cmd_cleanup = ('ip', 'link', 'del', veth_host)
692 try:
693 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
694 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700695 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800696 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500697
698 # Signal the child that the net ns/proxy is fully configured now.
699 ns_setup_lock.Post()
700 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800701
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400702 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800703
704
Mike Frysingera78a56e2012-11-20 06:02:30 -0500705def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700706 """Re-execute cros_sdk as root.
707
708 Also unshare the mount namespace so as to ensure that processes outside
709 the chroot can't mess with our mounts.
710 """
711 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500712 cmd = _SudoCommand() + ['--'] + argv
713 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500714 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400715 # We must set up the cgroups mounts before we enter our own namespace.
716 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800717 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700718
719
Mike Frysinger34db8692013-11-11 14:54:08 -0500720def _CreateParser(sdk_latest_version, bootstrap_latest_version):
721 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400722 usage = ('usage: %(prog)s [options] '
723 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
724 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
725 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700726
Mike Frysinger34db8692013-11-11 14:54:08 -0500727 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500728 default_chroot = os.path.join(constants.SOURCE_ROOT,
729 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400730 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700731 '--chroot', dest='chroot', default=default_chroot, type='path',
732 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600733 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
734 default=True,
735 help='Do not mount the chroot on a loopback image; '
736 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800737
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400738 parser.add_argument('--chrome_root', type='path',
739 help='Mount this chrome root into the SDK chroot')
740 parser.add_argument('--chrome_root_mount', type='path',
741 help='Mount chrome into this path inside SDK chroot')
742 parser.add_argument('--nousepkg', action='store_true', default=False,
743 help='Do not use binary packages when creating a chroot.')
744 parser.add_argument('-u', '--url', dest='sdk_url',
745 help='Use sdk tarball located at this url. Use file:// '
746 'for local files.')
747 parser.add_argument('--sdk-version',
748 help=('Use this sdk version. For prebuilt, current is %r'
749 ', for bootstrapping it is %r.'
750 % (sdk_latest_version, bootstrap_latest_version)))
751 parser.add_argument('--workspace',
752 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900753 parser.add_argument('--goma_dir', type='path',
754 help='Goma installed directory to mount into the chroot.')
755 parser.add_argument('--goma_client_json', type='path',
756 help='Service account json file to use goma on bot. '
757 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800758
759 # Use type=str instead of type='path' to prevent the given path from being
760 # transfered to absolute path automatically.
761 parser.add_argument('--working-dir', type=str,
762 help='Run the command in specific working directory in '
763 'chroot. If the given directory is a relative '
764 'path, this program will transfer the path to '
765 'the corresponding one inside chroot.')
766
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400767 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500768
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700769 # SDK overlay tarball options (mutually exclusive).
770 group = parser.add_mutually_exclusive_group()
771 group.add_argument('--toolchains',
772 help=('Comma-separated list of toolchains we expect to be '
773 'using on the chroot. Used for downloading a '
774 'corresponding SDK toolchains group (if one is '
775 'found), which may speed up chroot initialization '
776 'when building for the first time. Otherwise this '
777 'has no effect and will not restrict the chroot in '
778 'any way. Ignored if using --bootstrap.'))
779 group.add_argument('--board',
780 help=('The board we intend to be building in the chroot. '
781 'Used for deriving the list of required toolchains '
782 '(see --toolchains).'))
783
Mike Frysinger34db8692013-11-11 14:54:08 -0500784 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400785 group = parser.add_argument_group('Commands')
786 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500787 '--enter', action='store_true', default=False,
788 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400789 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500790 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500791 help='Create the chroot only if it does not already exist. '
792 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400793 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500794 '--bootstrap', action='store_true', default=False,
795 help='Build everything from scratch, including the sdk. '
796 'Use this only if you need to validate a change '
797 'that affects SDK creation itself (toolchain and '
798 'build are typically the only folk who need this). '
799 'Note this will quite heavily slow down the build. '
800 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400801 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500802 '-r', '--replace', action='store_true', default=False,
803 help='Replace an existing SDK chroot. Basically an alias '
804 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400805 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500806 '--delete', action='store_true', default=False,
807 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400808 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500809 '--download', action='store_true', default=False,
810 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600811 group.add_argument(
812 '--snapshot-create', metavar='SNAPSHOT_NAME',
813 help='Create a snapshot of the chroot. Requires that the chroot was '
814 'created without the --nouse-image option.')
815 group.add_argument(
816 '--snapshot-restore', metavar='SNAPSHOT_NAME',
817 help='Restore the chroot to a previously created snapshot.')
818 group.add_argument(
819 '--snapshot-delete', metavar='SNAPSHOT_NAME',
820 help='Delete a previously created snapshot. Deleting a snapshot that '
821 'does not exist is not an error.')
822 group.add_argument(
823 '--snapshot-list', action='store_true', default=False,
824 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500825 commands = group
826
Mike Frysinger80dfce92014-04-21 10:58:53 -0400827 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400828 group = parser.add_argument_group('Namespaces')
829 group.add_argument('--proxy-sim', action='store_true', default=False,
830 help='Simulate a restrictive network requiring an outbound'
831 ' proxy.')
832 group.add_argument('--no-ns-pid', dest='ns_pid',
833 default=True, action='store_false',
834 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400835
Mike Frysinger34db8692013-11-11 14:54:08 -0500836 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400837 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500838 'Internal Chromium OS Build Team Options',
839 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400840 group.add_argument('--buildbot-log-version', default=False,
841 action='store_true',
842 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500843
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400844 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500845
846
847def main(argv):
848 conf = cros_build_lib.LoadKeyValueFile(
849 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
850 ignore_missing=True)
851 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
852 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
853 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400854 options = parser.parse_args(argv)
855 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800856
857 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500858 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800859
Brian Harring1790ac42012-09-23 08:53:33 -0700860 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700861 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600862 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700863 "cros_sdk is currently only supported on x86_64; you're running"
864 " %s. Please find a x86_64 machine." % (host,))
865
Josh Triplett472a4182013-03-08 11:48:57 -0800866 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
867 if options.proxy_sim:
868 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600869 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800870
Benjamin Gordon040a1162017-06-29 13:44:47 -0600871 if (sdk_latest_version == '<unknown>' or
872 bootstrap_latest_version == '<unknown>'):
873 cros_build_lib.Die(
874 'No SDK version was found. '
875 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
876 'Please change to a directory inside your Chromium OS source tree\n'
877 'and retry. If you need to setup a Chromium OS source tree, see\n'
878 ' http://www.chromium.org/chromium-os/developer-guide')
879
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600880 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
881 or options.snapshot_delete or options.snapshot_list)
882 if any_snapshot_operation and not options.use_image:
883 cros_build_lib.Die('Snapshot operations are not compatible with '
884 '--nouse-image.')
885
886 if (options.snapshot_delete and options.snapshot_delete ==
887 options.snapshot_restore):
888 parser.error('Cannot --snapshot_delete the same snapshot you are '
889 'restoring with --snapshot_restore.')
890
David James471532c2013-01-21 10:23:31 -0800891 _ReExecuteIfNeeded([sys.argv[0]] + argv)
892
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600893 lock_path = os.path.dirname(options.chroot)
894 lock_path = os.path.join(
895 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
896
Brian Harring218e13c2012-10-10 16:21:26 -0700897 # Expand out the aliases...
898 if options.replace:
899 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800900
Brian Harring218e13c2012-10-10 16:21:26 -0700901 if options.bootstrap:
902 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800903
Brian Harring218e13c2012-10-10 16:21:26 -0700904 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400905 # pylint: disable=protected-access
906 # This _group_actions access sucks, but upstream decided to not include an
907 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700908 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400909 for x in commands._group_actions)
910 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700911 options.enter |= bool(chroot_command)
912
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600913 if (options.delete and not options.create and
914 (options.enter or any_snapshot_operation)):
915 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700916 "was specified makes no sense.")
917
Yong Hong84ba9172018-02-07 01:37:42 +0800918 if options.working_dir is not None and not os.path.isabs(options.working_dir):
919 options.working_dir = path_util.ToChrootPath(options.working_dir)
920
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600921 # Clean up potential leftovers from previous interrupted builds.
922 # TODO(bmgordon): Remove this at the end of 2017. That should be long enough
923 # to get rid of them all.
924 chroot_build_path = options.chroot + '.build'
925 if options.use_image and os.path.exists(chroot_build_path):
926 try:
927 with cgroups.SimpleContainChildren('cros_sdk'):
928 with locking.FileLock(lock_path, 'chroot lock') as lock:
929 logging.notice('Cleaning up leftover build directory %s',
930 chroot_build_path)
931 lock.write_lock()
932 osutils.UmountTree(chroot_build_path)
933 osutils.RmDir(chroot_build_path)
934 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600935 logging.warning('Unable to remove %s: %s', chroot_build_path, e)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600936
Benjamin Gordon35194f12017-07-19 10:26:22 -0600937 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600938 chroot_ver_file = os.path.join(options.chroot, 'etc', 'cros_chroot_version')
939 chroot_exists = os.path.exists(chroot_ver_file)
940 if (options.use_image and not chroot_exists and not options.delete and
941 not missing_image_tools and
942 os.path.exists(_ImageFileForChroot(options.chroot))):
943 # Try to re-mount an existing image in case the user has rebooted.
944 with cgroups.SimpleContainChildren('cros_sdk'):
945 with locking.FileLock(lock_path, 'chroot lock') as lock:
946 logging.debug('Checking if existing chroot image can be mounted.')
947 lock.write_lock()
948 cros_build_lib.MountChroot(options.chroot, create=False)
949 chroot_exists = os.path.exists(chroot_ver_file)
950 if chroot_exists:
951 logging.notice('Mounted existing image %s on chroot',
952 _ImageFileForChroot(options.chroot))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600953 if (options.create or options.enter or options.snapshot_create or
954 options.snapshot_restore):
Brian Harring218e13c2012-10-10 16:21:26 -0700955 # Only create if it's being wiped, or if it doesn't exist.
956 if not options.delete and chroot_exists:
957 options.create = False
958 else:
959 options.download = True
960
961 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600962 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700963 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800964
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600965 # Anything that needs to manipulate the main chroot mount or communicate with
966 # LVM needs to be done here before we enter the new namespaces.
967
968 # If deleting, do it regardless of the use_image flag so that a
969 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600970 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
971 # favor of this block.
972 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600973 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600974 with cgroups.SimpleContainChildren('cros_sdk'):
975 with locking.FileLock(lock_path, 'chroot lock') as lock:
976 lock.write_lock()
977 if missing_image_tools:
978 logging.notice('Unmounting chroot.')
979 osutils.UmountTree(options.chroot)
980 else:
981 logging.notice('Deleting chroot.')
982 cros_build_lib.CleanupChrootMount(options.chroot, delete_image=True)
983 osutils.RmDir(options.chroot, ignore_missing=True)
984 chroot_deleted = True
985
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600986 # Make sure the main chroot mount is visible. Contents will be filled in
987 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600988 if options.create and options.use_image:
989 if missing_image_tools:
990 raise SystemExit(
991 '''The tool(s) %s were not found.
992Please make sure the lvm2 and thin-provisioning-tools packages
993are installed on your host.
994Example(ubuntu):
995 sudo apt-get install lvm2 thin-provisioning-tools
996
997If you want to run without lvm2, pass --nouse-image (chroot
998snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600999
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001000 logging.debug('Making sure chroot image is mounted.')
1001 with cgroups.SimpleContainChildren('cros_sdk'):
1002 with locking.FileLock(lock_path, 'chroot lock') as lock:
1003 lock.write_lock()
1004 if not cros_build_lib.MountChroot(options.chroot, create=True):
1005 cros_build_lib.Die('Unable to mount %s on chroot',
1006 _ImageFileForChroot(options.chroot))
1007 logging.notice('Mounted %s on chroot',
1008 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001009
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001010 # Snapshot operations will always need the VG/LV, but other actions won't.
1011 if any_snapshot_operation:
1012 with cgroups.SimpleContainChildren('cros_sdk'):
1013 with locking.FileLock(lock_path, 'chroot lock') as lock:
1014 chroot_vg, chroot_lv = cros_build_lib.FindChrootMountSource(
1015 options.chroot)
1016 if not chroot_vg or not chroot_lv:
1017 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
1018 options.chroot)
1019
1020 # Delete snapshot before creating a new one. This allows the user to
1021 # throw out old state, create a new snapshot, and enter the chroot in a
1022 # single call to cros_sdk. Since restore involves deleting, also do it
1023 # before creating.
1024 if options.snapshot_restore:
1025 lock.write_lock()
1026 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1027 if options.snapshot_restore not in valid_snapshots:
1028 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
1029 'Valid snapshots: %s', options.snapshot_restore,
1030 ', '.join(valid_snapshots))
1031 osutils.UmountTree(options.chroot)
1032 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1033 chroot_lv):
1034 cros_build_lib.Die('Unable to restore chroot to snapshot.')
1035 if not cros_build_lib.MountChroot(options.chroot, create=False):
1036 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1037
1038 # Use a read lock for snapshot delete and create even though they modify
1039 # the filesystem, because they don't modify the mounted chroot itself.
1040 # The underlying LVM commands take their own locks, so conflicting
1041 # concurrent operations here may crash cros_sdk, but won't corrupt the
1042 # chroot image. This tradeoff seems worth it to allow snapshot
1043 # operations on chroots that have a process inside.
1044 if options.snapshot_delete:
1045 lock.read_lock()
1046 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1047
1048 if options.snapshot_create:
1049 lock.read_lock()
1050 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1051 chroot_lv):
1052 cros_build_lib.Die('Unable to create snapshot.')
1053
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001054 img_path = _ImageFileForChroot(options.chroot)
1055 if (options.use_image and os.path.exists(options.chroot) and
1056 os.path.exists(img_path)):
1057 img_stat = os.stat(img_path)
1058 img_used_bytes = img_stat.st_blocks * 512
1059
1060 mount_stat = os.statvfs(options.chroot)
1061 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1062 mount_stat.f_bfree)
1063
1064 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1065 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1066 logging.notice('%s is using %s GiB more than needed. Running '
1067 'fstrim.', img_path, extra_gbs)
1068 cmd = ['fstrim', options.chroot]
1069 try:
1070 cros_build_lib.RunCommand(cmd, print_cmd=False)
1071 except cros_build_lib.RunCommandError as e:
1072 logging.warning('Running fstrim failed. Consider running fstrim on '
1073 'your chroot manually.\nError: %s', e)
1074
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001075 # Enter a new set of namespaces. Everything after here cannot directly affect
1076 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001077 namespaces.SimpleUnshare()
1078 if options.ns_pid:
1079 first_pid = namespaces.CreatePidNs()
1080 else:
1081 first_pid = None
1082
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001083 if options.snapshot_list:
1084 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1085 print(snap)
1086 sys.exit(0)
1087
Brian Harringb938c782012-02-29 15:14:38 -08001088 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001089 sdk_version = (bootstrap_latest_version if options.bootstrap
1090 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001091 else:
1092 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001093 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001094 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001095
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001096 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -07001097 if options.sdk_url:
1098 urls = [options.sdk_url]
1099 elif options.bootstrap:
1100 urls = GetStage3Urls(sdk_version)
1101 else:
1102 urls = GetArchStageTarballs(sdk_version)
1103
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001104 # Get URLs for the toolchains overlay, if one is to be used.
1105 toolchains_overlay_urls = None
1106 if not options.bootstrap:
1107 toolchains = None
1108 if options.toolchains:
1109 toolchains = options.toolchains.split(',')
1110 elif options.board:
1111 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
1112
1113 if toolchains:
1114 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
1115 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001116
Mike Frysinger80dfce92014-04-21 10:58:53 -04001117 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001118 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001119 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -07001120
Josh Triplett472a4182013-03-08 11:48:57 -08001121 if options.proxy_sim:
1122 _ProxySimSetup(options)
1123
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001124 if (options.delete and not chroot_deleted and
1125 (os.path.exists(options.chroot) or
1126 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001127 lock.write_lock()
1128 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001129
David James56e6c2c2012-10-24 23:54:41 -07001130 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1131 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001132 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001133
David James56e6c2c2012-10-24 23:54:41 -07001134 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001135 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001136 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001137 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001138 continue
1139 lock.write_lock(
1140 "Upgrade to %r needed but chroot is locked; please exit "
1141 "all instances so this upgrade can finish." % src)
1142 if not os.path.exists(src):
1143 # Note that while waiting for the write lock, src may've vanished;
1144 # it's a rare race during the upgrade process that's a byproduct
1145 # of us avoiding taking a write lock to do the src check. If we
1146 # took a write lock for that check, it would effectively limit
1147 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001148 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001149 elif not os.path.exists(target):
1150 # Upgrade occurred, but a reversion, or something whacky
1151 # occurred writing to the old location. Wipe and continue.
1152 os.rename(src, target)
1153 else:
1154 # Upgrade occurred once already, but either a reversion or
1155 # some before/after separate cros_sdk usage is at play.
1156 # Wipe and continue.
1157 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001158
David James56e6c2c2012-10-24 23:54:41 -07001159 if options.download:
1160 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001161 sdk_tarball = FetchRemoteTarballs(
1162 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
1163 if toolchains_overlay_urls:
1164 toolchains_overlay_tarball = FetchRemoteTarballs(
1165 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
1166 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -07001167
David James56e6c2c2012-10-24 23:54:41 -07001168 if options.create:
1169 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001170 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001171 options.cache_dir,
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001172 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001173
David James56e6c2c2012-10-24 23:54:41 -07001174 if options.enter:
1175 lock.read_lock()
1176 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001177 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001178 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001179 options.working_dir, chroot_command)