blob: 4977d51f92fa6ba676517840a38e9082355e448a [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 Arnoldecc86fa2015-05-22 12:06:04 -0700100def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500101 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200102
103 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500104 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200105 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700106 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700107 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200108
109 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700110 Full path to the downloaded file, or None if |allow_none| and no URL worked.
111
112 Raises:
113 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200114 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200115
Brian Harring1790ac42012-09-23 08:53:33 -0700116 # Note we track content length ourselves since certain versions of curl
117 # fail if asked to resume a complete file.
118 # pylint: disable=C0301,W0631
119 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700120 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700121 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200122 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700123 # http://www.logilab.org/ticket/8766
124 # pylint: disable=E1101
125 parsed = urlparse.urlparse(url)
126 tarball_name = os.path.basename(parsed.path)
127 if parsed.scheme in ('', 'file'):
128 if os.path.exists(parsed.path):
129 return parsed.path
130 continue
131 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700132 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800133 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900134 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
135 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700136 successful = False
137 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700138 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700139 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700140 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700141 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500142 elif header.lower().startswith('content-length:'):
143 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700144 if successful:
145 break
146 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200147 break
148 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700149 if allow_none:
150 return None
151 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200152
Brian Harringae0a5322012-09-15 01:46:51 -0700153 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700154 current_size = 0
155 if os.path.exists(tarball_dest):
156 current_size = os.path.getsize(tarball_dest)
157 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700158 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700159 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100160
Brian Harring1790ac42012-09-23 08:53:33 -0700161 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800162 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900163 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
164 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800165
Brian Harring1790ac42012-09-23 08:53:33 -0700166 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700167 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
168 # because we might have partial overlap between known prefixes.
169 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
170 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
171 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700172 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700173 if (filename == tarball_name or
174 any([(filename.startswith(p) and
175 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
176 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700177 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700178 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700179 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200180
Brian Harringb938c782012-02-29 15:14:38 -0800181 return tarball_dest
182
183
Benjamin Gordon589873b2018-05-31 14:30:56 -0600184def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600185 """Creates a new chroot from a given SDK.
186
187 Args:
188 chroot_path: Path where the new chroot will be created.
189 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600190 cache_dir: Path to a directory that will be used for caching portage files,
191 etc.
192 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
193 chroot.
194 """
Brian Harringb938c782012-02-29 15:14:38 -0800195
Brian Harring1790ac42012-09-23 08:53:33 -0700196 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700197 '--chroot', chroot_path,
198 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700199
Mike Frysinger2de7f042012-07-10 04:45:03 -0400200 if nousepkg:
201 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800202
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700203 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800204 try:
205 cros_build_lib.RunCommand(cmd, print_cmd=False)
206 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700207 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800208
209
210def DeleteChroot(chroot_path):
211 """Deletes an existing chroot"""
212 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
213 '--delete']
214 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700215 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800216 cros_build_lib.RunCommand(cmd, print_cmd=False)
217 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700218 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800219
220
Brian Harringae0a5322012-09-15 01:46:51 -0700221def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Yong Hong84ba9172018-02-07 01:37:42 +0800222 workspace, goma_dir, goma_client_json, working_dir,
223 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800224 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400225 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
226 # The os.ST_NOSUID constant wasn't added until python-3.2.
227 if st.f_flag & 0x2:
228 cros_build_lib.Die('chroot cannot be in a nosuid mount')
229
Brian Harringae0a5322012-09-15 01:46:51 -0700230 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800231 if chrome_root:
232 cmd.extend(['--chrome_root', chrome_root])
233 if chrome_root_mount:
234 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700235 if workspace:
236 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900237 if goma_dir:
238 cmd.extend(['--goma_dir', goma_dir])
239 if goma_client_json:
240 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800241 if working_dir is not None:
242 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700243
Brian Harringb938c782012-02-29 15:14:38 -0800244 if len(additional_args) > 0:
245 cmd.append('--')
246 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700247
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700248 # ThinLTO opens lots of files at the same time.
249 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700250 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
251 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700252 # If we were in interactive mode, ignore the exit code; it'll be whatever
253 # they last ran w/in the chroot and won't matter to us one way or another.
254 # Note this does allow chroot entrance to fail and be ignored during
255 # interactive; this is however a rare case and the user will immediately
256 # see it (nor will they be checking the exit code manually).
257 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700258 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800259
260
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600261def _ImageFileForChroot(chroot):
262 """Find the image file that should be associated with |chroot|.
263
264 This function does not check if the image exists; it simply returns the
265 filename that would be used.
266
267 Args:
268 chroot: Path to the chroot.
269
270 Returns:
271 Path to an image file that would be associated with chroot.
272 """
273 return chroot.rstrip('/') + '.img'
274
275
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600276def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
277 """Create a snapshot for the specified chroot VG/LV.
278
279 Args:
280 snapshot_name: The name of the new snapshot.
281 chroot_vg: The name of the VG containing the origin LV.
282 chroot_lv: The name of the origin LV.
283
284 Returns:
285 True if the snapshot was created, or False if a snapshot with the same
286 name already exists.
287
288 Raises:
289 SystemExit: The lvcreate command failed.
290 """
291 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
292 logging.error('Cannot create snapshot %s: A volume with that name already '
293 'exists.', snapshot_name)
294 return False
295
296 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
297 chroot_vg, chroot_lv)]
298 try:
299 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
300 chroot_lv, chroot_vg)
301 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
302 return True
303 except cros_build_lib.RunCommandError:
304 raise SystemExit('Running %r failed!' % cmd)
305
306
307def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
308 """Delete the named snapshot from the specified chroot VG.
309
310 If the requested snapshot is not found, nothing happens. The main chroot LV
311 and internal thinpool LV cannot be deleted with this function.
312
313 Args:
314 snapshot_name: The name of the snapshot to delete.
315 chroot_vg: The name of the VG containing the origin LV.
316 chroot_lv: The name of the origin LV.
317
318 Raises:
319 SystemExit: The lvremove command failed.
320 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600321 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
322 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600323 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
324 'if you want to remove the whole chroot.', snapshot_name)
325 return
326
327 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
328 return
329
330 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
331 try:
332 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
333 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
334 except cros_build_lib.RunCommandError:
335 raise SystemExit('Running %r failed!' % cmd)
336
337
338def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
339 """Restore the chroot to an existing snapshot.
340
341 This is done by renaming the original |chroot_lv| LV to a temporary name,
342 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
343 now unused LV. If an error occurs, attempts to rename the original snapshot
344 back to |chroot_lv| to leave the chroot unchanged.
345
346 The chroot must be unmounted before calling this function, and will be left
347 unmounted after this function returns.
348
349 Args:
350 snapshot_name: The name of the snapshot to restore. This snapshot will no
351 longer be accessible at its original name after this function finishes.
352 chroot_vg: The VG containing the chroot LV and snapshot LV.
353 chroot_lv: The name of the original chroot LV.
354
355 Returns:
356 True if the chroot was restored to the requested snapshot, or False if
357 the snapshot wasn't found or isn't valid.
358
359 Raises:
360 SystemExit: Any of the LVM commands failed.
361 """
362 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600363 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
364 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600365 snapshot_name not in valid_snapshots):
366 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
367 snapshot_name, ', '.join(valid_snapshots))
368 return False
369
370 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
371 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
372 try:
373 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
374 except cros_build_lib.RunCommandError:
375 raise SystemExit('Running %r failed!' % cmd)
376
377 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
378 try:
379 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
380 except cros_build_lib.RunCommandError:
381 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
382 try:
383 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
384 except cros_build_lib.RunCommandError:
385 raise SystemExit('Failed to rename %s to chroot and failed to restore '
386 '%s back to chroot. Failed command: %r' %
387 (snapshot_name, backup_chroot_name, cmd))
388 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
389 'been restored. Failed command: %r' %
390 (snapshot_name, cmd))
391
392 # Some versions of LVM set snapshots to be skipped at auto-activate time.
393 # Other versions don't have this flag at all. We run lvchange to try
394 # disabling auto-skip and activating the volume, but ignore errors. Versions
395 # that don't have the flag should be auto-activated.
396 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
397 cmd = ['lvchange', '-kn', chroot_lv_path]
398 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
399 error_code_ok=True)
400
401 # Activate the LV in case the lvchange above was needed. Activating an LV
402 # that is already active shouldn't do anything, so this is safe to run even if
403 # the -kn wasn't needed.
404 cmd = ['lvchange', '-ay', chroot_lv_path]
405 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
406
407 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
408 try:
409 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
410 except cros_build_lib.RunCommandError:
411 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
412 (chroot_vg, backup_chroot_name, cmd))
413
414 return True
415
416
417def ListChrootSnapshots(chroot_vg, chroot_lv):
418 """Return all snapshots in |chroot_vg| regardless of origin volume.
419
420 Args:
421 chroot_vg: The name of the VG containing the chroot.
422 chroot_lv: The name of the chroot LV.
423
424 Returns:
425 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
426
427 Raises:
428 SystemExit: The lvs command failed.
429 """
430 if not chroot_vg or not chroot_lv:
431 return []
432
433 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
434 '--noheadings', '--separator', '\t', chroot_vg]
435 try:
436 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
437 redirect_stdout=True)
438 except cros_build_lib.RunCommandError:
439 raise SystemExit('Running %r failed!' % cmd)
440
441 # Once the thin origin volume has been deleted, there's no way to tell a
442 # snapshot apart from any other volume. Since this VG is created and managed
443 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
444 # valid snapshots.
445 snapshots = []
446 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
447 for line in result.output.splitlines():
448 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
449 if (lv_name == chroot_lv or
Benjamin Gordon74645232018-05-04 17:40:42 -0600450 lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
451 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600452 not snapshot_attrs.match(lv_attr)):
453 continue
454 snapshots.append(lv_name)
455 return snapshots
456
457
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600458def _FindSubmounts(*args):
459 """Find all mounts matching each of the paths in |args| and any submounts.
460
461 Returns:
462 A list of all matching mounts in the order found in /proc/mounts.
463 """
464 mounts = []
465 paths = [p.rstrip('/') for p in args]
466 for mtab in osutils.IterateMountPoints():
467 for path in paths:
468 if mtab.destination == path or mtab.destination.startswith(path + '/'):
469 mounts.append(mtab.destination)
470 break
471
472 return mounts
473
474
David James56e6c2c2012-10-24 23:54:41 -0700475def _SudoCommand():
476 """Get the 'sudo' command, along with all needed environment variables."""
477
David James5a73b4d2013-03-07 10:23:40 -0800478 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
479 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700480 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800481 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700482 value = os.environ.get(key)
483 if value is not None:
484 cmd += ['%s=%s' % (key, value)]
485
486 # Pass in the path to the depot_tools so that users can access them from
487 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400488 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500489
David James56e6c2c2012-10-24 23:54:41 -0700490 return cmd
491
492
Josh Triplett472a4182013-03-08 11:48:57 -0800493def _ReportMissing(missing):
494 """Report missing utilities, then exit.
495
496 Args:
497 missing: List of missing utilities, as returned by
498 osutils.FindMissingBinaries. If non-empty, will not return.
499 """
500
501 if missing:
502 raise SystemExit(
503 'The tool(s) %s were not found.\n'
504 'Please install the appropriate package in your host.\n'
505 'Example(ubuntu):\n'
506 ' sudo apt-get install <packagename>'
507 % ', '.join(missing))
508
509
510def _ProxySimSetup(options):
511 """Set up proxy simulator, and return only in the child environment.
512
513 TODO: Ideally, this should support multiple concurrent invocations of
514 cros_sdk --proxy-sim; currently, such invocations will conflict with each
515 other due to the veth device names and IP addresses. Either this code would
516 need to generate fresh, unused names for all of these before forking, or it
517 would need to support multiple concurrent cros_sdk invocations sharing one
518 proxy and allowing it to exit when unused (without counting on any local
519 service-management infrastructure on the host).
520 """
521
522 may_need_mpm = False
523 apache_bin = osutils.Which('apache2')
524 if apache_bin is None:
525 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
526 if apache_bin is None:
527 _ReportMissing(('apache2',))
528 else:
529 may_need_mpm = True
530
531 # Module names and .so names included for ease of grepping.
532 apache_modules = [('proxy_module', 'mod_proxy.so'),
533 ('proxy_connect_module', 'mod_proxy_connect.so'),
534 ('proxy_http_module', 'mod_proxy_http.so'),
535 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
536
537 # Find the apache module directory, and make sure it has the modules we need.
538 module_dirs = {}
539 for g in PROXY_APACHE_MODULE_GLOBS:
540 for mod, so in apache_modules:
541 for f in glob.glob(os.path.join(g, so)):
542 module_dirs.setdefault(os.path.dirname(f), []).append(so)
543 for apache_module_path, modules_found in module_dirs.iteritems():
544 if len(modules_found) == len(apache_modules):
545 break
546 else:
547 # Appease cros lint, which doesn't understand that this else block will not
548 # fall through to the subsequent code which relies on apache_module_path.
549 apache_module_path = None
550 raise SystemExit(
551 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500552 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800553
554 def check_add_module(name):
555 so = 'mod_%s.so' % name
556 if os.access(os.path.join(apache_module_path, so), os.F_OK):
557 mod = '%s_module' % name
558 apache_modules.append((mod, so))
559 return True
560 return False
561
562 check_add_module('authz_core')
563 if may_need_mpm:
564 for mpm in PROXY_APACHE_MPMS:
565 if check_add_module('mpm_%s' % mpm):
566 break
567
568 veth_host = '%s-host' % PROXY_VETH_PREFIX
569 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
570
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500571 # Set up locks to sync the net namespace setup. We need the child to create
572 # the net ns first, and then have the parent assign the guest end of the veth
573 # interface to the child's new network namespace & bring up the proxy. Only
574 # then can the child move forward and rely on the network being up.
575 ns_create_lock = locking.PipeLock()
576 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800577
578 pid = os.fork()
579 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500580 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800581 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500582
583 # Signal the parent the ns is ready to be configured.
584 ns_create_lock.Post()
585 del ns_create_lock
586
587 # Wait for the parent to finish setting up the ns/proxy.
588 ns_setup_lock.Wait()
589 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800590
591 # Set up child side of the network.
592 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500593 ('ip', 'link', 'set', 'up', 'lo'),
594 ('ip', 'address', 'add',
595 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
596 'dev', veth_guest),
597 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800598 )
599 try:
600 for cmd in commands:
601 cros_build_lib.RunCommand(cmd, print_cmd=False)
602 except cros_build_lib.RunCommandError:
603 raise SystemExit('Running %r failed!' % (cmd,))
604
605 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
606 for proto in ('http', 'https', 'ftp'):
607 os.environ[proto + '_proxy'] = proxy_url
608 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
609 os.environ.pop(v, None)
610 return
611
Josh Triplett472a4182013-03-08 11:48:57 -0800612 # Set up parent side of the network.
613 uid = int(os.environ.get('SUDO_UID', '0'))
614 gid = int(os.environ.get('SUDO_GID', '0'))
615 if uid == 0 or gid == 0:
616 for username in PROXY_APACHE_FALLBACK_USERS:
617 try:
618 pwnam = pwd.getpwnam(username)
619 uid, gid = pwnam.pw_uid, pwnam.pw_gid
620 break
621 except KeyError:
622 continue
623 if uid == 0 or gid == 0:
624 raise SystemExit('Could not find a non-root user to run Apache as')
625
626 chroot_parent, chroot_base = os.path.split(options.chroot)
627 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
628 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
629
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500630 # Wait for the child to create the net ns.
631 ns_create_lock.Wait()
632 del ns_create_lock
633
Josh Triplett472a4182013-03-08 11:48:57 -0800634 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500635 'User #%u' % uid,
636 'Group #%u' % gid,
637 'PidFile %s' % pid_file,
638 'ErrorLog %s' % log_file,
639 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
640 'ServerName %s' % PROXY_HOST_IP,
641 'ProxyRequests On',
642 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800643 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500644 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
645 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800646 ]
647 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500648 ('ip', 'link', 'add', 'name', veth_host,
649 'type', 'veth', 'peer', 'name', veth_guest),
650 ('ip', 'address', 'add',
651 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
652 'dev', veth_host),
653 ('ip', 'link', 'set', veth_host, 'up'),
654 ([apache_bin, '-f', '/dev/null'] +
655 [arg for d in apache_directives for arg in ('-C', d)]),
656 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800657 )
658 cmd = None # Make cros lint happy.
659 try:
660 for cmd in commands:
661 cros_build_lib.RunCommand(cmd, print_cmd=False)
662 except cros_build_lib.RunCommandError:
663 # Clean up existing interfaces, if any.
664 cmd_cleanup = ('ip', 'link', 'del', veth_host)
665 try:
666 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
667 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700668 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800669 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500670
671 # Signal the child that the net ns/proxy is fully configured now.
672 ns_setup_lock.Post()
673 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800674
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400675 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800676
677
Mike Frysingera78a56e2012-11-20 06:02:30 -0500678def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700679 """Re-execute cros_sdk as root.
680
681 Also unshare the mount namespace so as to ensure that processes outside
682 the chroot can't mess with our mounts.
683 """
684 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500685 cmd = _SudoCommand() + ['--'] + argv
686 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500687 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400688 # We must set up the cgroups mounts before we enter our own namespace.
689 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800690 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700691
692
Mike Frysinger34db8692013-11-11 14:54:08 -0500693def _CreateParser(sdk_latest_version, bootstrap_latest_version):
694 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400695 usage = ('usage: %(prog)s [options] '
696 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
697 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
698 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700699
Mike Frysinger34db8692013-11-11 14:54:08 -0500700 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500701 default_chroot = os.path.join(constants.SOURCE_ROOT,
702 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400703 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700704 '--chroot', dest='chroot', default=default_chroot, type='path',
705 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600706 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
707 default=True,
708 help='Do not mount the chroot on a loopback image; '
709 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800710
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400711 parser.add_argument('--chrome_root', type='path',
712 help='Mount this chrome root into the SDK chroot')
713 parser.add_argument('--chrome_root_mount', type='path',
714 help='Mount chrome into this path inside SDK chroot')
715 parser.add_argument('--nousepkg', action='store_true', default=False,
716 help='Do not use binary packages when creating a chroot.')
717 parser.add_argument('-u', '--url', dest='sdk_url',
718 help='Use sdk tarball located at this url. Use file:// '
719 'for local files.')
720 parser.add_argument('--sdk-version',
721 help=('Use this sdk version. For prebuilt, current is %r'
722 ', for bootstrapping it is %r.'
723 % (sdk_latest_version, bootstrap_latest_version)))
724 parser.add_argument('--workspace',
725 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900726 parser.add_argument('--goma_dir', type='path',
727 help='Goma installed directory to mount into the chroot.')
728 parser.add_argument('--goma_client_json', type='path',
729 help='Service account json file to use goma on bot. '
730 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800731
732 # Use type=str instead of type='path' to prevent the given path from being
733 # transfered to absolute path automatically.
734 parser.add_argument('--working-dir', type=str,
735 help='Run the command in specific working directory in '
736 'chroot. If the given directory is a relative '
737 'path, this program will transfer the path to '
738 'the corresponding one inside chroot.')
739
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400740 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500741
742 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400743 group = parser.add_argument_group('Commands')
744 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500745 '--enter', action='store_true', default=False,
746 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400747 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500748 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500749 help='Create the chroot only if it does not already exist. '
750 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400751 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500752 '--bootstrap', action='store_true', default=False,
753 help='Build everything from scratch, including the sdk. '
754 'Use this only if you need to validate a change '
755 'that affects SDK creation itself (toolchain and '
756 'build are typically the only folk who need this). '
757 'Note this will quite heavily slow down the build. '
758 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400759 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500760 '-r', '--replace', action='store_true', default=False,
761 help='Replace an existing SDK chroot. Basically an alias '
762 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400763 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500764 '--delete', action='store_true', default=False,
765 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400766 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500767 '--download', action='store_true', default=False,
768 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600769 group.add_argument(
770 '--snapshot-create', metavar='SNAPSHOT_NAME',
771 help='Create a snapshot of the chroot. Requires that the chroot was '
772 'created without the --nouse-image option.')
773 group.add_argument(
774 '--snapshot-restore', metavar='SNAPSHOT_NAME',
775 help='Restore the chroot to a previously created snapshot.')
776 group.add_argument(
777 '--snapshot-delete', metavar='SNAPSHOT_NAME',
778 help='Delete a previously created snapshot. Deleting a snapshot that '
779 'does not exist is not an error.')
780 group.add_argument(
781 '--snapshot-list', action='store_true', default=False,
782 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500783 commands = group
784
Mike Frysinger80dfce92014-04-21 10:58:53 -0400785 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400786 group = parser.add_argument_group('Namespaces')
787 group.add_argument('--proxy-sim', action='store_true', default=False,
788 help='Simulate a restrictive network requiring an outbound'
789 ' proxy.')
790 group.add_argument('--no-ns-pid', dest='ns_pid',
791 default=True, action='store_false',
792 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400793
Mike Frysinger34db8692013-11-11 14:54:08 -0500794 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400795 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500796 'Internal Chromium OS Build Team Options',
797 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400798 group.add_argument('--buildbot-log-version', default=False,
799 action='store_true',
800 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500801
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400802 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500803
804
805def main(argv):
806 conf = cros_build_lib.LoadKeyValueFile(
807 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
808 ignore_missing=True)
809 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
810 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
811 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400812 options = parser.parse_args(argv)
813 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800814
815 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500816 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800817
Brian Harring1790ac42012-09-23 08:53:33 -0700818 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700819 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600820 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700821 "cros_sdk is currently only supported on x86_64; you're running"
822 " %s. Please find a x86_64 machine." % (host,))
823
Josh Triplett472a4182013-03-08 11:48:57 -0800824 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
825 if options.proxy_sim:
826 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600827 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800828
Benjamin Gordon040a1162017-06-29 13:44:47 -0600829 if (sdk_latest_version == '<unknown>' or
830 bootstrap_latest_version == '<unknown>'):
831 cros_build_lib.Die(
832 'No SDK version was found. '
833 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
834 'Please change to a directory inside your Chromium OS source tree\n'
835 'and retry. If you need to setup a Chromium OS source tree, see\n'
836 ' http://www.chromium.org/chromium-os/developer-guide')
837
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600838 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
839 or options.snapshot_delete or options.snapshot_list)
840 if any_snapshot_operation and not options.use_image:
841 cros_build_lib.Die('Snapshot operations are not compatible with '
842 '--nouse-image.')
843
844 if (options.snapshot_delete and options.snapshot_delete ==
845 options.snapshot_restore):
846 parser.error('Cannot --snapshot_delete the same snapshot you are '
847 'restoring with --snapshot_restore.')
848
David James471532c2013-01-21 10:23:31 -0800849 _ReExecuteIfNeeded([sys.argv[0]] + argv)
850
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600851 lock_path = os.path.dirname(options.chroot)
852 lock_path = os.path.join(
853 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
854
Brian Harring218e13c2012-10-10 16:21:26 -0700855 # Expand out the aliases...
856 if options.replace:
857 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800858
Brian Harring218e13c2012-10-10 16:21:26 -0700859 if options.bootstrap:
860 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800861
Brian Harring218e13c2012-10-10 16:21:26 -0700862 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400863 # pylint: disable=protected-access
864 # This _group_actions access sucks, but upstream decided to not include an
865 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700866 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400867 for x in commands._group_actions)
868 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700869 options.enter |= bool(chroot_command)
870
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600871 if (options.delete and not options.create and
872 (options.enter or any_snapshot_operation)):
873 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700874 "was specified makes no sense.")
875
Yong Hong84ba9172018-02-07 01:37:42 +0800876 if options.working_dir is not None and not os.path.isabs(options.working_dir):
877 options.working_dir = path_util.ToChrootPath(options.working_dir)
878
Benjamin Gordon35194f12017-07-19 10:26:22 -0600879 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600880 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600881 if (options.use_image and not chroot_exists and not options.delete and
882 not missing_image_tools and
883 os.path.exists(_ImageFileForChroot(options.chroot))):
884 # Try to re-mount an existing image in case the user has rebooted.
885 with cgroups.SimpleContainChildren('cros_sdk'):
886 with locking.FileLock(lock_path, 'chroot lock') as lock:
887 logging.debug('Checking if existing chroot image can be mounted.')
888 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600889 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600890 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600891 if chroot_exists:
892 logging.notice('Mounted existing image %s on chroot',
893 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700894
895 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600896 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700897 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800898
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600899 # Make sure we will download if we plan to create.
900 options.download |= options.create
901
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600902 # Anything that needs to manipulate the main chroot mount or communicate with
903 # LVM needs to be done here before we enter the new namespaces.
904
905 # If deleting, do it regardless of the use_image flag so that a
906 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600907 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
908 # favor of this block.
909 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600910 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600911 with cgroups.SimpleContainChildren('cros_sdk'):
912 with locking.FileLock(lock_path, 'chroot lock') as lock:
913 lock.write_lock()
914 if missing_image_tools:
915 logging.notice('Unmounting chroot.')
916 osutils.UmountTree(options.chroot)
917 else:
918 logging.notice('Deleting chroot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600919 cros_sdk_lib.CleanupChrootMount(options.chroot, delete_image=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600920 osutils.RmDir(options.chroot, ignore_missing=True)
921 chroot_deleted = True
922
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600923 # Make sure the main chroot mount is visible. Contents will be filled in
924 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600925 if options.create and options.use_image:
926 if missing_image_tools:
927 raise SystemExit(
928 '''The tool(s) %s were not found.
929Please make sure the lvm2 and thin-provisioning-tools packages
930are installed on your host.
931Example(ubuntu):
932 sudo apt-get install lvm2 thin-provisioning-tools
933
934If you want to run without lvm2, pass --nouse-image (chroot
935snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600936
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600937 logging.debug('Making sure chroot image is mounted.')
938 with cgroups.SimpleContainChildren('cros_sdk'):
939 with locking.FileLock(lock_path, 'chroot lock') as lock:
940 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600941 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600942 cros_build_lib.Die('Unable to mount %s on chroot',
943 _ImageFileForChroot(options.chroot))
944 logging.notice('Mounted %s on chroot',
945 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600946
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600947 # Snapshot operations will always need the VG/LV, but other actions won't.
948 if any_snapshot_operation:
949 with cgroups.SimpleContainChildren('cros_sdk'):
950 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -0600951 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600952 options.chroot)
953 if not chroot_vg or not chroot_lv:
954 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
955 options.chroot)
956
957 # Delete snapshot before creating a new one. This allows the user to
958 # throw out old state, create a new snapshot, and enter the chroot in a
959 # single call to cros_sdk. Since restore involves deleting, also do it
960 # before creating.
961 if options.snapshot_restore:
962 lock.write_lock()
963 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
964 if options.snapshot_restore not in valid_snapshots:
965 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
966 'Valid snapshots: %s', options.snapshot_restore,
967 ', '.join(valid_snapshots))
968 osutils.UmountTree(options.chroot)
969 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
970 chroot_lv):
971 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600972 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600973 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
974
975 # Use a read lock for snapshot delete and create even though they modify
976 # the filesystem, because they don't modify the mounted chroot itself.
977 # The underlying LVM commands take their own locks, so conflicting
978 # concurrent operations here may crash cros_sdk, but won't corrupt the
979 # chroot image. This tradeoff seems worth it to allow snapshot
980 # operations on chroots that have a process inside.
981 if options.snapshot_delete:
982 lock.read_lock()
983 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
984
985 if options.snapshot_create:
986 lock.read_lock()
987 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
988 chroot_lv):
989 cros_build_lib.Die('Unable to create snapshot.')
990
Benjamin Gordone3d5bd12017-11-16 15:42:28 -0700991 img_path = _ImageFileForChroot(options.chroot)
992 if (options.use_image and os.path.exists(options.chroot) and
993 os.path.exists(img_path)):
994 img_stat = os.stat(img_path)
995 img_used_bytes = img_stat.st_blocks * 512
996
997 mount_stat = os.statvfs(options.chroot)
998 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
999 mount_stat.f_bfree)
1000
1001 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1002 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1003 logging.notice('%s is using %s GiB more than needed. Running '
1004 'fstrim.', img_path, extra_gbs)
1005 cmd = ['fstrim', options.chroot]
1006 try:
1007 cros_build_lib.RunCommand(cmd, print_cmd=False)
1008 except cros_build_lib.RunCommandError as e:
1009 logging.warning('Running fstrim failed. Consider running fstrim on '
1010 'your chroot manually.\nError: %s', e)
1011
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001012 # Enter a new set of namespaces. Everything after here cannot directly affect
1013 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001014 namespaces.SimpleUnshare()
1015 if options.ns_pid:
1016 first_pid = namespaces.CreatePidNs()
1017 else:
1018 first_pid = None
1019
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001020 if options.snapshot_list:
1021 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1022 print(snap)
1023 sys.exit(0)
1024
Brian Harringb938c782012-02-29 15:14:38 -08001025 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001026 sdk_version = (bootstrap_latest_version if options.bootstrap
1027 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001028 else:
1029 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001030 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001031 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001032
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001033 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001034 if options.download:
1035 if options.sdk_url:
1036 urls = [options.sdk_url]
1037 elif options.bootstrap:
1038 urls = GetStage3Urls(sdk_version)
1039 else:
1040 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001041
Mike Frysinger80dfce92014-04-21 10:58:53 -04001042 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001043 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001044 if options.proxy_sim:
1045 _ProxySimSetup(options)
1046
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001047 if (options.delete and not chroot_deleted and
1048 (os.path.exists(options.chroot) or
1049 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001050 lock.write_lock()
1051 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001052
David James56e6c2c2012-10-24 23:54:41 -07001053 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1054 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001055 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001056
David James56e6c2c2012-10-24 23:54:41 -07001057 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001058 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001059 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001060 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001061 continue
1062 lock.write_lock(
1063 "Upgrade to %r needed but chroot is locked; please exit "
1064 "all instances so this upgrade can finish." % src)
1065 if not os.path.exists(src):
1066 # Note that while waiting for the write lock, src may've vanished;
1067 # it's a rare race during the upgrade process that's a byproduct
1068 # of us avoiding taking a write lock to do the src check. If we
1069 # took a write lock for that check, it would effectively limit
1070 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001071 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001072 elif not os.path.exists(target):
1073 # Upgrade occurred, but a reversion, or something whacky
1074 # occurred writing to the old location. Wipe and continue.
1075 os.rename(src, target)
1076 else:
1077 # Upgrade occurred once already, but either a reversion or
1078 # some before/after separate cros_sdk usage is at play.
1079 # Wipe and continue.
1080 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001081
David James56e6c2c2012-10-24 23:54:41 -07001082 if options.download:
1083 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001084 sdk_tarball = FetchRemoteTarballs(
1085 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001086
David James56e6c2c2012-10-24 23:54:41 -07001087 if options.create:
1088 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001089 # Recheck if the chroot is set up here before creating to make sure we
1090 # account for whatever the various delete/unmount/remount steps above
1091 # have done.
1092 if cros_sdk_lib.IsChrootReady(options.chroot):
1093 logging.debug('Chroot already exists. Skipping creation.')
1094 else:
1095 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
1096 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001097
David James56e6c2c2012-10-24 23:54:41 -07001098 if options.enter:
1099 lock.read_lock()
1100 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001101 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001102 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001103 options.working_dir, chroot_command)