blob: 3280308ac6511b1f7a7b1d6d4835601cecb90bf2 [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
Benjamin Gordon74645232018-05-04 17:40:42 -060032from chromite.lib import cros_sdk_lib
Brian Harringb938c782012-02-29 15:14:38 -080033from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080034from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070035from chromite.lib import osutils
Yong Hong84ba9172018-02-07 01:37:42 +080036from chromite.lib import path_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040037from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080038from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050039from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080040
41cros_build_lib.STRICT_SUDO = True
42
43
Zdenek Behanaa52cea2012-05-30 01:31:11 +020044COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020045
Brian Harringb938c782012-02-29 15:14:38 -080046# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050047MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
48 'src/scripts/sdk_lib/make_chroot.sh')]
49ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
50 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080051
Josh Triplett472a4182013-03-08 11:48:57 -080052# Proxy simulator configuration.
53PROXY_HOST_IP = '192.168.240.1'
54PROXY_PORT = 8080
55PROXY_GUEST_IP = '192.168.240.2'
56PROXY_NETMASK = 30
57PROXY_VETH_PREFIX = 'veth'
58PROXY_CONNECT_PORTS = (80, 443, 9418)
59PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
60PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
61PROXY_APACHE_FALLBACK_PATH = ':'.join(
62 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
63)
64PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
65
Josh Triplett9a495f62013-03-15 18:06:55 -070066# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080067NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080068
Josh Triplett472a4182013-03-08 11:48:57 -080069# Tools needed for --proxy-sim only.
70PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080071
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060072# Tools needed when use_image is true (the default).
73IMAGE_NEEDED_TOOLS = ('losetup', 'lvchange', 'lvcreate', 'lvs', 'mke2fs',
Benjamin Gordoncfa9c162017-08-03 13:49:29 -060074 'pvscan', 'thin_check', 'vgchange', 'vgcreate', 'vgs')
Benjamin Gordon386b9eb2017-07-20 09:21:33 -060075
Benjamin Gordone3d5bd12017-11-16 15:42:28 -070076# As space is used inside the chroot, the empty space in chroot.img is
77# allocated. Deleting files inside the chroot doesn't automatically return the
78# used space to the OS. Over time, this tends to make the sparse chroot.img
79# less sparse even if the chroot contents don't currently need much space. We
80# can recover most of this unused space with fstrim, but that takes too much
81# time to run it every time. Instead, check the used space against the image
82# size after mounting the chroot and only call fstrim if it looks like we could
83# recover at least this many GiB.
84MAX_UNUSED_IMAGE_GBS = 20
85
Mike Frysingercc838832014-05-24 13:10:30 -040086
Brian Harring1790ac42012-09-23 08:53:33 -070087def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080088 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070089 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050090 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
91 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070092 for compressor in COMPRESSION_PREFERENCE]
93
94
95def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050096 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070097 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080098
99
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700100def GetToolchainsOverlayUrls(version, toolchains):
101 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700102
103 Args:
104 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
105 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700106 overlays are stored (.../2015/05/ in this case).
107 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700108
109 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700110 List of alternative download URLs for an SDK overlay tarball that contains
111 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700112 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700113 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700114 suburl_template = os.path.join(
115 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700116 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
117 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700118 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
119 for ext in COMPRESSION_PREFERENCE]
120
121
122def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500123 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200124
125 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500126 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200127 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700128 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700129 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200130
131 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700132 Full path to the downloaded file, or None if |allow_none| and no URL worked.
133
134 Raises:
135 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200136 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200137
Brian Harring1790ac42012-09-23 08:53:33 -0700138 # Note we track content length ourselves since certain versions of curl
139 # fail if asked to resume a complete file.
140 # pylint: disable=C0301,W0631
141 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700142 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700143 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200144 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700145 # http://www.logilab.org/ticket/8766
146 # pylint: disable=E1101
147 parsed = urlparse.urlparse(url)
148 tarball_name = os.path.basename(parsed.path)
149 if parsed.scheme in ('', 'file'):
150 if os.path.exists(parsed.path):
151 return parsed.path
152 continue
153 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700154 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800155 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900156 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
157 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700158 successful = False
159 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700160 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700161 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700162 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700163 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500164 elif header.lower().startswith('content-length:'):
165 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700166 if successful:
167 break
168 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200169 break
170 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700171 if allow_none:
172 return None
173 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200174
Brian Harringae0a5322012-09-15 01:46:51 -0700175 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700176 current_size = 0
177 if os.path.exists(tarball_dest):
178 current_size = os.path.getsize(tarball_dest)
179 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700180 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700181 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100182
Brian Harring1790ac42012-09-23 08:53:33 -0700183 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800184 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900185 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
186 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800187
Brian Harring1790ac42012-09-23 08:53:33 -0700188 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700189 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
190 # because we might have partial overlap between known prefixes.
191 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
192 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
193 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700194 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700195 if (filename == tarball_name or
196 any([(filename.startswith(p) and
197 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
198 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700199 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700200 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700201 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200202
Brian Harringb938c782012-02-29 15:14:38 -0800203 return tarball_dest
204
205
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700206def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600207 cache_dir, nousepkg=False):
208 """Creates a new chroot from a given SDK.
209
210 Args:
211 chroot_path: Path where the new chroot will be created.
212 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
213 toolchains_overlay_tarball: Optional path to a second tarball that will be
214 unpacked into the chroot on top of the SDK tarball.
215 cache_dir: Path to a directory that will be used for caching portage files,
216 etc.
217 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
218 chroot.
219 """
Brian Harringb938c782012-02-29 15:14:38 -0800220
Brian Harring1790ac42012-09-23 08:53:33 -0700221 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700222 '--chroot', chroot_path,
223 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700224
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700225 if toolchains_overlay_tarball:
226 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700227
Mike Frysinger2de7f042012-07-10 04:45:03 -0400228 if nousepkg:
229 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800230
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700231 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800232 try:
233 cros_build_lib.RunCommand(cmd, print_cmd=False)
234 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700235 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800236
237
238def DeleteChroot(chroot_path):
239 """Deletes an existing chroot"""
240 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
241 '--delete']
242 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700243 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800244 cros_build_lib.RunCommand(cmd, print_cmd=False)
245 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700246 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800247
248
Brian Harringae0a5322012-09-15 01:46:51 -0700249def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Yong Hong84ba9172018-02-07 01:37:42 +0800250 workspace, goma_dir, goma_client_json, working_dir,
251 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800252 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400253 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
254 # The os.ST_NOSUID constant wasn't added until python-3.2.
255 if st.f_flag & 0x2:
256 cros_build_lib.Die('chroot cannot be in a nosuid mount')
257
Brian Harringae0a5322012-09-15 01:46:51 -0700258 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800259 if chrome_root:
260 cmd.extend(['--chrome_root', chrome_root])
261 if chrome_root_mount:
262 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700263 if workspace:
264 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900265 if goma_dir:
266 cmd.extend(['--goma_dir', goma_dir])
267 if goma_client_json:
268 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800269 if working_dir is not None:
270 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700271
Brian Harringb938c782012-02-29 15:14:38 -0800272 if len(additional_args) > 0:
273 cmd.append('--')
274 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700275
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700276 # ThinLTO opens lots of files at the same time.
277 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700278 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
279 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700280 # If we were in interactive mode, ignore the exit code; it'll be whatever
281 # they last ran w/in the chroot and won't matter to us one way or another.
282 # Note this does allow chroot entrance to fail and be ignored during
283 # interactive; this is however a rare case and the user will immediately
284 # see it (nor will they be checking the exit code manually).
285 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700286 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800287
288
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600289def _ImageFileForChroot(chroot):
290 """Find the image file that should be associated with |chroot|.
291
292 This function does not check if the image exists; it simply returns the
293 filename that would be used.
294
295 Args:
296 chroot: Path to the chroot.
297
298 Returns:
299 Path to an image file that would be associated with chroot.
300 """
301 return chroot.rstrip('/') + '.img'
302
303
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600304def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
305 """Create a snapshot for the specified chroot VG/LV.
306
307 Args:
308 snapshot_name: The name of the new snapshot.
309 chroot_vg: The name of the VG containing the origin LV.
310 chroot_lv: The name of the origin LV.
311
312 Returns:
313 True if the snapshot was created, or False if a snapshot with the same
314 name already exists.
315
316 Raises:
317 SystemExit: The lvcreate command failed.
318 """
319 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
320 logging.error('Cannot create snapshot %s: A volume with that name already '
321 'exists.', snapshot_name)
322 return False
323
324 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
325 chroot_vg, chroot_lv)]
326 try:
327 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
328 chroot_lv, chroot_vg)
329 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
330 return True
331 except cros_build_lib.RunCommandError:
332 raise SystemExit('Running %r failed!' % cmd)
333
334
335def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
336 """Delete the named snapshot from the specified chroot VG.
337
338 If the requested snapshot is not found, nothing happens. The main chroot LV
339 and internal thinpool LV cannot be deleted with this function.
340
341 Args:
342 snapshot_name: The name of the snapshot to delete.
343 chroot_vg: The name of the VG containing the origin LV.
344 chroot_lv: The name of the origin LV.
345
346 Raises:
347 SystemExit: The lvremove command failed.
348 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600349 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
350 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600351 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
352 'if you want to remove the whole chroot.', snapshot_name)
353 return
354
355 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
356 return
357
358 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
359 try:
360 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
361 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
362 except cros_build_lib.RunCommandError:
363 raise SystemExit('Running %r failed!' % cmd)
364
365
366def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
367 """Restore the chroot to an existing snapshot.
368
369 This is done by renaming the original |chroot_lv| LV to a temporary name,
370 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
371 now unused LV. If an error occurs, attempts to rename the original snapshot
372 back to |chroot_lv| to leave the chroot unchanged.
373
374 The chroot must be unmounted before calling this function, and will be left
375 unmounted after this function returns.
376
377 Args:
378 snapshot_name: The name of the snapshot to restore. This snapshot will no
379 longer be accessible at its original name after this function finishes.
380 chroot_vg: The VG containing the chroot LV and snapshot LV.
381 chroot_lv: The name of the original chroot LV.
382
383 Returns:
384 True if the chroot was restored to the requested snapshot, or False if
385 the snapshot wasn't found or isn't valid.
386
387 Raises:
388 SystemExit: Any of the LVM commands failed.
389 """
390 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600391 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
392 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600393 snapshot_name not in valid_snapshots):
394 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
395 snapshot_name, ', '.join(valid_snapshots))
396 return False
397
398 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
399 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
400 try:
401 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
402 except cros_build_lib.RunCommandError:
403 raise SystemExit('Running %r failed!' % cmd)
404
405 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
406 try:
407 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
408 except cros_build_lib.RunCommandError:
409 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
410 try:
411 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
412 except cros_build_lib.RunCommandError:
413 raise SystemExit('Failed to rename %s to chroot and failed to restore '
414 '%s back to chroot. Failed command: %r' %
415 (snapshot_name, backup_chroot_name, cmd))
416 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
417 'been restored. Failed command: %r' %
418 (snapshot_name, cmd))
419
420 # Some versions of LVM set snapshots to be skipped at auto-activate time.
421 # Other versions don't have this flag at all. We run lvchange to try
422 # disabling auto-skip and activating the volume, but ignore errors. Versions
423 # that don't have the flag should be auto-activated.
424 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
425 cmd = ['lvchange', '-kn', chroot_lv_path]
426 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
427 error_code_ok=True)
428
429 # Activate the LV in case the lvchange above was needed. Activating an LV
430 # that is already active shouldn't do anything, so this is safe to run even if
431 # the -kn wasn't needed.
432 cmd = ['lvchange', '-ay', chroot_lv_path]
433 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
434
435 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
436 try:
437 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
438 except cros_build_lib.RunCommandError:
439 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
440 (chroot_vg, backup_chroot_name, cmd))
441
442 return True
443
444
445def ListChrootSnapshots(chroot_vg, chroot_lv):
446 """Return all snapshots in |chroot_vg| regardless of origin volume.
447
448 Args:
449 chroot_vg: The name of the VG containing the chroot.
450 chroot_lv: The name of the chroot LV.
451
452 Returns:
453 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
454
455 Raises:
456 SystemExit: The lvs command failed.
457 """
458 if not chroot_vg or not chroot_lv:
459 return []
460
461 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
462 '--noheadings', '--separator', '\t', chroot_vg]
463 try:
464 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
465 redirect_stdout=True)
466 except cros_build_lib.RunCommandError:
467 raise SystemExit('Running %r failed!' % cmd)
468
469 # Once the thin origin volume has been deleted, there's no way to tell a
470 # snapshot apart from any other volume. Since this VG is created and managed
471 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
472 # valid snapshots.
473 snapshots = []
474 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
475 for line in result.output.splitlines():
476 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
477 if (lv_name == chroot_lv or
Benjamin Gordon74645232018-05-04 17:40:42 -0600478 lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
479 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600480 not snapshot_attrs.match(lv_attr)):
481 continue
482 snapshots.append(lv_name)
483 return snapshots
484
485
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600486def _FindSubmounts(*args):
487 """Find all mounts matching each of the paths in |args| and any submounts.
488
489 Returns:
490 A list of all matching mounts in the order found in /proc/mounts.
491 """
492 mounts = []
493 paths = [p.rstrip('/') for p in args]
494 for mtab in osutils.IterateMountPoints():
495 for path in paths:
496 if mtab.destination == path or mtab.destination.startswith(path + '/'):
497 mounts.append(mtab.destination)
498 break
499
500 return mounts
501
502
David James56e6c2c2012-10-24 23:54:41 -0700503def _SudoCommand():
504 """Get the 'sudo' command, along with all needed environment variables."""
505
David James5a73b4d2013-03-07 10:23:40 -0800506 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
507 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700508 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800509 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700510 value = os.environ.get(key)
511 if value is not None:
512 cmd += ['%s=%s' % (key, value)]
513
514 # Pass in the path to the depot_tools so that users can access them from
515 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400516 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500517
David James56e6c2c2012-10-24 23:54:41 -0700518 return cmd
519
520
Josh Triplett472a4182013-03-08 11:48:57 -0800521def _ReportMissing(missing):
522 """Report missing utilities, then exit.
523
524 Args:
525 missing: List of missing utilities, as returned by
526 osutils.FindMissingBinaries. If non-empty, will not return.
527 """
528
529 if missing:
530 raise SystemExit(
531 'The tool(s) %s were not found.\n'
532 'Please install the appropriate package in your host.\n'
533 'Example(ubuntu):\n'
534 ' sudo apt-get install <packagename>'
535 % ', '.join(missing))
536
537
538def _ProxySimSetup(options):
539 """Set up proxy simulator, and return only in the child environment.
540
541 TODO: Ideally, this should support multiple concurrent invocations of
542 cros_sdk --proxy-sim; currently, such invocations will conflict with each
543 other due to the veth device names and IP addresses. Either this code would
544 need to generate fresh, unused names for all of these before forking, or it
545 would need to support multiple concurrent cros_sdk invocations sharing one
546 proxy and allowing it to exit when unused (without counting on any local
547 service-management infrastructure on the host).
548 """
549
550 may_need_mpm = False
551 apache_bin = osutils.Which('apache2')
552 if apache_bin is None:
553 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
554 if apache_bin is None:
555 _ReportMissing(('apache2',))
556 else:
557 may_need_mpm = True
558
559 # Module names and .so names included for ease of grepping.
560 apache_modules = [('proxy_module', 'mod_proxy.so'),
561 ('proxy_connect_module', 'mod_proxy_connect.so'),
562 ('proxy_http_module', 'mod_proxy_http.so'),
563 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
564
565 # Find the apache module directory, and make sure it has the modules we need.
566 module_dirs = {}
567 for g in PROXY_APACHE_MODULE_GLOBS:
568 for mod, so in apache_modules:
569 for f in glob.glob(os.path.join(g, so)):
570 module_dirs.setdefault(os.path.dirname(f), []).append(so)
571 for apache_module_path, modules_found in module_dirs.iteritems():
572 if len(modules_found) == len(apache_modules):
573 break
574 else:
575 # Appease cros lint, which doesn't understand that this else block will not
576 # fall through to the subsequent code which relies on apache_module_path.
577 apache_module_path = None
578 raise SystemExit(
579 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500580 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800581
582 def check_add_module(name):
583 so = 'mod_%s.so' % name
584 if os.access(os.path.join(apache_module_path, so), os.F_OK):
585 mod = '%s_module' % name
586 apache_modules.append((mod, so))
587 return True
588 return False
589
590 check_add_module('authz_core')
591 if may_need_mpm:
592 for mpm in PROXY_APACHE_MPMS:
593 if check_add_module('mpm_%s' % mpm):
594 break
595
596 veth_host = '%s-host' % PROXY_VETH_PREFIX
597 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
598
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500599 # Set up locks to sync the net namespace setup. We need the child to create
600 # the net ns first, and then have the parent assign the guest end of the veth
601 # interface to the child's new network namespace & bring up the proxy. Only
602 # then can the child move forward and rely on the network being up.
603 ns_create_lock = locking.PipeLock()
604 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800605
606 pid = os.fork()
607 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500608 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800609 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500610
611 # Signal the parent the ns is ready to be configured.
612 ns_create_lock.Post()
613 del ns_create_lock
614
615 # Wait for the parent to finish setting up the ns/proxy.
616 ns_setup_lock.Wait()
617 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800618
619 # Set up child side of the network.
620 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500621 ('ip', 'link', 'set', 'up', 'lo'),
622 ('ip', 'address', 'add',
623 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
624 'dev', veth_guest),
625 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800626 )
627 try:
628 for cmd in commands:
629 cros_build_lib.RunCommand(cmd, print_cmd=False)
630 except cros_build_lib.RunCommandError:
631 raise SystemExit('Running %r failed!' % (cmd,))
632
633 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
634 for proto in ('http', 'https', 'ftp'):
635 os.environ[proto + '_proxy'] = proxy_url
636 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
637 os.environ.pop(v, None)
638 return
639
Josh Triplett472a4182013-03-08 11:48:57 -0800640 # Set up parent side of the network.
641 uid = int(os.environ.get('SUDO_UID', '0'))
642 gid = int(os.environ.get('SUDO_GID', '0'))
643 if uid == 0 or gid == 0:
644 for username in PROXY_APACHE_FALLBACK_USERS:
645 try:
646 pwnam = pwd.getpwnam(username)
647 uid, gid = pwnam.pw_uid, pwnam.pw_gid
648 break
649 except KeyError:
650 continue
651 if uid == 0 or gid == 0:
652 raise SystemExit('Could not find a non-root user to run Apache as')
653
654 chroot_parent, chroot_base = os.path.split(options.chroot)
655 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
656 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
657
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500658 # Wait for the child to create the net ns.
659 ns_create_lock.Wait()
660 del ns_create_lock
661
Josh Triplett472a4182013-03-08 11:48:57 -0800662 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500663 'User #%u' % uid,
664 'Group #%u' % gid,
665 'PidFile %s' % pid_file,
666 'ErrorLog %s' % log_file,
667 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
668 'ServerName %s' % PROXY_HOST_IP,
669 'ProxyRequests On',
670 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800671 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500672 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
673 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800674 ]
675 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500676 ('ip', 'link', 'add', 'name', veth_host,
677 'type', 'veth', 'peer', 'name', veth_guest),
678 ('ip', 'address', 'add',
679 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
680 'dev', veth_host),
681 ('ip', 'link', 'set', veth_host, 'up'),
682 ([apache_bin, '-f', '/dev/null'] +
683 [arg for d in apache_directives for arg in ('-C', d)]),
684 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800685 )
686 cmd = None # Make cros lint happy.
687 try:
688 for cmd in commands:
689 cros_build_lib.RunCommand(cmd, print_cmd=False)
690 except cros_build_lib.RunCommandError:
691 # Clean up existing interfaces, if any.
692 cmd_cleanup = ('ip', 'link', 'del', veth_host)
693 try:
694 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
695 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700696 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800697 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500698
699 # Signal the child that the net ns/proxy is fully configured now.
700 ns_setup_lock.Post()
701 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800702
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400703 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800704
705
Mike Frysingera78a56e2012-11-20 06:02:30 -0500706def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700707 """Re-execute cros_sdk as root.
708
709 Also unshare the mount namespace so as to ensure that processes outside
710 the chroot can't mess with our mounts.
711 """
712 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500713 cmd = _SudoCommand() + ['--'] + argv
714 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500715 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400716 # We must set up the cgroups mounts before we enter our own namespace.
717 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800718 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700719
720
Mike Frysinger34db8692013-11-11 14:54:08 -0500721def _CreateParser(sdk_latest_version, bootstrap_latest_version):
722 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400723 usage = ('usage: %(prog)s [options] '
724 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
725 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
726 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700727
Mike Frysinger34db8692013-11-11 14:54:08 -0500728 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500729 default_chroot = os.path.join(constants.SOURCE_ROOT,
730 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400731 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700732 '--chroot', dest='chroot', default=default_chroot, type='path',
733 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600734 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
735 default=True,
736 help='Do not mount the chroot on a loopback image; '
737 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800738
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400739 parser.add_argument('--chrome_root', type='path',
740 help='Mount this chrome root into the SDK chroot')
741 parser.add_argument('--chrome_root_mount', type='path',
742 help='Mount chrome into this path inside SDK chroot')
743 parser.add_argument('--nousepkg', action='store_true', default=False,
744 help='Do not use binary packages when creating a chroot.')
745 parser.add_argument('-u', '--url', dest='sdk_url',
746 help='Use sdk tarball located at this url. Use file:// '
747 'for local files.')
748 parser.add_argument('--sdk-version',
749 help=('Use this sdk version. For prebuilt, current is %r'
750 ', for bootstrapping it is %r.'
751 % (sdk_latest_version, bootstrap_latest_version)))
752 parser.add_argument('--workspace',
753 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900754 parser.add_argument('--goma_dir', type='path',
755 help='Goma installed directory to mount into the chroot.')
756 parser.add_argument('--goma_client_json', type='path',
757 help='Service account json file to use goma on bot. '
758 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800759
760 # Use type=str instead of type='path' to prevent the given path from being
761 # transfered to absolute path automatically.
762 parser.add_argument('--working-dir', type=str,
763 help='Run the command in specific working directory in '
764 'chroot. If the given directory is a relative '
765 'path, this program will transfer the path to '
766 'the corresponding one inside chroot.')
767
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400768 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500769
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700770 # SDK overlay tarball options (mutually exclusive).
771 group = parser.add_mutually_exclusive_group()
772 group.add_argument('--toolchains',
773 help=('Comma-separated list of toolchains we expect to be '
774 'using on the chroot. Used for downloading a '
775 'corresponding SDK toolchains group (if one is '
776 'found), which may speed up chroot initialization '
777 'when building for the first time. Otherwise this '
778 'has no effect and will not restrict the chroot in '
779 'any way. Ignored if using --bootstrap.'))
780 group.add_argument('--board',
781 help=('The board we intend to be building in the chroot. '
782 'Used for deriving the list of required toolchains '
783 '(see --toolchains).'))
784
Mike Frysinger34db8692013-11-11 14:54:08 -0500785 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400786 group = parser.add_argument_group('Commands')
787 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500788 '--enter', action='store_true', default=False,
789 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400790 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500791 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500792 help='Create the chroot only if it does not already exist. '
793 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400794 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500795 '--bootstrap', action='store_true', default=False,
796 help='Build everything from scratch, including the sdk. '
797 'Use this only if you need to validate a change '
798 'that affects SDK creation itself (toolchain and '
799 'build are typically the only folk who need this). '
800 'Note this will quite heavily slow down the build. '
801 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400802 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500803 '-r', '--replace', action='store_true', default=False,
804 help='Replace an existing SDK chroot. Basically an alias '
805 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400806 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500807 '--delete', action='store_true', default=False,
808 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400809 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500810 '--download', action='store_true', default=False,
811 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600812 group.add_argument(
813 '--snapshot-create', metavar='SNAPSHOT_NAME',
814 help='Create a snapshot of the chroot. Requires that the chroot was '
815 'created without the --nouse-image option.')
816 group.add_argument(
817 '--snapshot-restore', metavar='SNAPSHOT_NAME',
818 help='Restore the chroot to a previously created snapshot.')
819 group.add_argument(
820 '--snapshot-delete', metavar='SNAPSHOT_NAME',
821 help='Delete a previously created snapshot. Deleting a snapshot that '
822 'does not exist is not an error.')
823 group.add_argument(
824 '--snapshot-list', action='store_true', default=False,
825 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500826 commands = group
827
Mike Frysinger80dfce92014-04-21 10:58:53 -0400828 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400829 group = parser.add_argument_group('Namespaces')
830 group.add_argument('--proxy-sim', action='store_true', default=False,
831 help='Simulate a restrictive network requiring an outbound'
832 ' proxy.')
833 group.add_argument('--no-ns-pid', dest='ns_pid',
834 default=True, action='store_false',
835 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400836
Mike Frysinger34db8692013-11-11 14:54:08 -0500837 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400838 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500839 'Internal Chromium OS Build Team Options',
840 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400841 group.add_argument('--buildbot-log-version', default=False,
842 action='store_true',
843 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500844
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400845 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500846
847
848def main(argv):
849 conf = cros_build_lib.LoadKeyValueFile(
850 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
851 ignore_missing=True)
852 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
853 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
854 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400855 options = parser.parse_args(argv)
856 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800857
858 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500859 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800860
Brian Harring1790ac42012-09-23 08:53:33 -0700861 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700862 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600863 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700864 "cros_sdk is currently only supported on x86_64; you're running"
865 " %s. Please find a x86_64 machine." % (host,))
866
Josh Triplett472a4182013-03-08 11:48:57 -0800867 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
868 if options.proxy_sim:
869 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600870 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800871
Benjamin Gordon040a1162017-06-29 13:44:47 -0600872 if (sdk_latest_version == '<unknown>' or
873 bootstrap_latest_version == '<unknown>'):
874 cros_build_lib.Die(
875 'No SDK version was found. '
876 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
877 'Please change to a directory inside your Chromium OS source tree\n'
878 'and retry. If you need to setup a Chromium OS source tree, see\n'
879 ' http://www.chromium.org/chromium-os/developer-guide')
880
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600881 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
882 or options.snapshot_delete or options.snapshot_list)
883 if any_snapshot_operation and not options.use_image:
884 cros_build_lib.Die('Snapshot operations are not compatible with '
885 '--nouse-image.')
886
887 if (options.snapshot_delete and options.snapshot_delete ==
888 options.snapshot_restore):
889 parser.error('Cannot --snapshot_delete the same snapshot you are '
890 'restoring with --snapshot_restore.')
891
David James471532c2013-01-21 10:23:31 -0800892 _ReExecuteIfNeeded([sys.argv[0]] + argv)
893
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600894 lock_path = os.path.dirname(options.chroot)
895 lock_path = os.path.join(
896 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
897
Brian Harring218e13c2012-10-10 16:21:26 -0700898 # Expand out the aliases...
899 if options.replace:
900 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800901
Brian Harring218e13c2012-10-10 16:21:26 -0700902 if options.bootstrap:
903 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800904
Brian Harring218e13c2012-10-10 16:21:26 -0700905 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400906 # pylint: disable=protected-access
907 # This _group_actions access sucks, but upstream decided to not include an
908 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700909 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400910 for x in commands._group_actions)
911 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700912 options.enter |= bool(chroot_command)
913
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600914 if (options.delete and not options.create and
915 (options.enter or any_snapshot_operation)):
916 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700917 "was specified makes no sense.")
918
Yong Hong84ba9172018-02-07 01:37:42 +0800919 if options.working_dir is not None and not os.path.isabs(options.working_dir):
920 options.working_dir = path_util.ToChrootPath(options.working_dir)
921
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600922 # Clean up potential leftovers from previous interrupted builds.
923 # TODO(bmgordon): Remove this at the end of 2017. That should be long enough
924 # to get rid of them all.
925 chroot_build_path = options.chroot + '.build'
926 if options.use_image and os.path.exists(chroot_build_path):
927 try:
928 with cgroups.SimpleContainChildren('cros_sdk'):
929 with locking.FileLock(lock_path, 'chroot lock') as lock:
930 logging.notice('Cleaning up leftover build directory %s',
931 chroot_build_path)
932 lock.write_lock()
933 osutils.UmountTree(chroot_build_path)
934 osutils.RmDir(chroot_build_path)
935 except cros_build_lib.RunCommandError as e:
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600936 logging.warning('Unable to remove %s: %s', chroot_build_path, e)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600937
Benjamin Gordon35194f12017-07-19 10:26:22 -0600938 # Discern if we need to create the chroot.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600939 chroot_ver_file = os.path.join(options.chroot, 'etc', 'cros_chroot_version')
940 chroot_exists = os.path.exists(chroot_ver_file)
941 if (options.use_image and not chroot_exists and not options.delete and
942 not missing_image_tools and
943 os.path.exists(_ImageFileForChroot(options.chroot))):
944 # Try to re-mount an existing image in case the user has rebooted.
945 with cgroups.SimpleContainChildren('cros_sdk'):
946 with locking.FileLock(lock_path, 'chroot lock') as lock:
947 logging.debug('Checking if existing chroot image can be mounted.')
948 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600949 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600950 chroot_exists = os.path.exists(chroot_ver_file)
951 if chroot_exists:
952 logging.notice('Mounted existing image %s on chroot',
953 _ImageFileForChroot(options.chroot))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600954 if (options.create or options.enter or options.snapshot_create or
955 options.snapshot_restore):
Brian Harring218e13c2012-10-10 16:21:26 -0700956 # Only create if it's being wiped, or if it doesn't exist.
957 if not options.delete and chroot_exists:
958 options.create = False
959 else:
960 options.download = True
961
962 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600963 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700964 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800965
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600966 # Anything that needs to manipulate the main chroot mount or communicate with
967 # LVM needs to be done here before we enter the new namespaces.
968
969 # If deleting, do it regardless of the use_image flag so that a
970 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600971 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
972 # favor of this block.
973 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600974 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600975 with cgroups.SimpleContainChildren('cros_sdk'):
976 with locking.FileLock(lock_path, 'chroot lock') as lock:
977 lock.write_lock()
978 if missing_image_tools:
979 logging.notice('Unmounting chroot.')
980 osutils.UmountTree(options.chroot)
981 else:
982 logging.notice('Deleting chroot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600983 cros_sdk_lib.CleanupChrootMount(options.chroot, delete_image=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600984 osutils.RmDir(options.chroot, ignore_missing=True)
985 chroot_deleted = True
986
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600987 # Make sure the main chroot mount is visible. Contents will be filled in
988 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600989 if options.create and options.use_image:
990 if missing_image_tools:
991 raise SystemExit(
992 '''The tool(s) %s were not found.
993Please make sure the lvm2 and thin-provisioning-tools packages
994are installed on your host.
995Example(ubuntu):
996 sudo apt-get install lvm2 thin-provisioning-tools
997
998If you want to run without lvm2, pass --nouse-image (chroot
999snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001000
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001001 logging.debug('Making sure chroot image is mounted.')
1002 with cgroups.SimpleContainChildren('cros_sdk'):
1003 with locking.FileLock(lock_path, 'chroot lock') as lock:
1004 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -06001005 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001006 cros_build_lib.Die('Unable to mount %s on chroot',
1007 _ImageFileForChroot(options.chroot))
1008 logging.notice('Mounted %s on chroot',
1009 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001010
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001011 # Snapshot operations will always need the VG/LV, but other actions won't.
1012 if any_snapshot_operation:
1013 with cgroups.SimpleContainChildren('cros_sdk'):
1014 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -06001015 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001016 options.chroot)
1017 if not chroot_vg or not chroot_lv:
1018 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
1019 options.chroot)
1020
1021 # Delete snapshot before creating a new one. This allows the user to
1022 # throw out old state, create a new snapshot, and enter the chroot in a
1023 # single call to cros_sdk. Since restore involves deleting, also do it
1024 # before creating.
1025 if options.snapshot_restore:
1026 lock.write_lock()
1027 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
1028 if options.snapshot_restore not in valid_snapshots:
1029 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
1030 'Valid snapshots: %s', options.snapshot_restore,
1031 ', '.join(valid_snapshots))
1032 osutils.UmountTree(options.chroot)
1033 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
1034 chroot_lv):
1035 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -06001036 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001037 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1038
1039 # Use a read lock for snapshot delete and create even though they modify
1040 # the filesystem, because they don't modify the mounted chroot itself.
1041 # The underlying LVM commands take their own locks, so conflicting
1042 # concurrent operations here may crash cros_sdk, but won't corrupt the
1043 # chroot image. This tradeoff seems worth it to allow snapshot
1044 # operations on chroots that have a process inside.
1045 if options.snapshot_delete:
1046 lock.read_lock()
1047 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1048
1049 if options.snapshot_create:
1050 lock.read_lock()
1051 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1052 chroot_lv):
1053 cros_build_lib.Die('Unable to create snapshot.')
1054
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001055 img_path = _ImageFileForChroot(options.chroot)
1056 if (options.use_image and os.path.exists(options.chroot) and
1057 os.path.exists(img_path)):
1058 img_stat = os.stat(img_path)
1059 img_used_bytes = img_stat.st_blocks * 512
1060
1061 mount_stat = os.statvfs(options.chroot)
1062 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1063 mount_stat.f_bfree)
1064
1065 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1066 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1067 logging.notice('%s is using %s GiB more than needed. Running '
1068 'fstrim.', img_path, extra_gbs)
1069 cmd = ['fstrim', options.chroot]
1070 try:
1071 cros_build_lib.RunCommand(cmd, print_cmd=False)
1072 except cros_build_lib.RunCommandError as e:
1073 logging.warning('Running fstrim failed. Consider running fstrim on '
1074 'your chroot manually.\nError: %s', e)
1075
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001076 # Enter a new set of namespaces. Everything after here cannot directly affect
1077 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001078 namespaces.SimpleUnshare()
1079 if options.ns_pid:
1080 first_pid = namespaces.CreatePidNs()
1081 else:
1082 first_pid = None
1083
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001084 if options.snapshot_list:
1085 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1086 print(snap)
1087 sys.exit(0)
1088
Brian Harringb938c782012-02-29 15:14:38 -08001089 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001090 sdk_version = (bootstrap_latest_version if options.bootstrap
1091 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001092 else:
1093 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001094 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001095 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001096
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001097 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001098 if options.download:
1099 if options.sdk_url:
1100 urls = [options.sdk_url]
1101 elif options.bootstrap:
1102 urls = GetStage3Urls(sdk_version)
1103 else:
1104 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001105
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001106 # Get URLs for the toolchains overlay, if one is to be used.
1107 toolchains_overlay_urls = None
1108 if not options.bootstrap:
1109 toolchains = None
1110 if options.toolchains:
1111 toolchains = options.toolchains.split(',')
1112 elif options.board:
1113 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
1114
1115 if toolchains:
1116 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
1117 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001118
Mike Frysinger80dfce92014-04-21 10:58:53 -04001119 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001120 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001121 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -07001122
Josh Triplett472a4182013-03-08 11:48:57 -08001123 if options.proxy_sim:
1124 _ProxySimSetup(options)
1125
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001126 if (options.delete and not chroot_deleted and
1127 (os.path.exists(options.chroot) or
1128 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001129 lock.write_lock()
1130 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001131
David James56e6c2c2012-10-24 23:54:41 -07001132 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1133 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001134 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001135
David James56e6c2c2012-10-24 23:54:41 -07001136 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001137 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001138 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001139 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001140 continue
1141 lock.write_lock(
1142 "Upgrade to %r needed but chroot is locked; please exit "
1143 "all instances so this upgrade can finish." % src)
1144 if not os.path.exists(src):
1145 # Note that while waiting for the write lock, src may've vanished;
1146 # it's a rare race during the upgrade process that's a byproduct
1147 # of us avoiding taking a write lock to do the src check. If we
1148 # took a write lock for that check, it would effectively limit
1149 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001150 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001151 elif not os.path.exists(target):
1152 # Upgrade occurred, but a reversion, or something whacky
1153 # occurred writing to the old location. Wipe and continue.
1154 os.rename(src, target)
1155 else:
1156 # Upgrade occurred once already, but either a reversion or
1157 # some before/after separate cros_sdk usage is at play.
1158 # Wipe and continue.
1159 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001160
David James56e6c2c2012-10-24 23:54:41 -07001161 if options.download:
1162 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001163 sdk_tarball = FetchRemoteTarballs(
1164 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
1165 if toolchains_overlay_urls:
1166 toolchains_overlay_tarball = FetchRemoteTarballs(
1167 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
1168 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -07001169
David James56e6c2c2012-10-24 23:54:41 -07001170 if options.create:
1171 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001172 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001173 options.cache_dir,
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001174 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001175
David James56e6c2c2012-10-24 23:54:41 -07001176 if options.enter:
1177 lock.read_lock()
1178 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001179 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001180 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001181 options.working_dir, chroot_command)