blob: bf28d9649ee674d6d994c2826fe600601ccb4f06 [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
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600221def CleanupChroot(chroot_path):
222 """Unmounts a chroot and cleans up any associated devices."""
223 cros_sdk_lib.CleanupChrootMount(chroot_path, delete_image=False)
224
225
Brian Harringae0a5322012-09-15 01:46:51 -0700226def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Yong Hong84ba9172018-02-07 01:37:42 +0800227 workspace, goma_dir, goma_client_json, working_dir,
228 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800229 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400230 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
231 # The os.ST_NOSUID constant wasn't added until python-3.2.
232 if st.f_flag & 0x2:
233 cros_build_lib.Die('chroot cannot be in a nosuid mount')
234
Brian Harringae0a5322012-09-15 01:46:51 -0700235 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800236 if chrome_root:
237 cmd.extend(['--chrome_root', chrome_root])
238 if chrome_root_mount:
239 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700240 if workspace:
241 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900242 if goma_dir:
243 cmd.extend(['--goma_dir', goma_dir])
244 if goma_client_json:
245 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800246 if working_dir is not None:
247 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700248
Brian Harringb938c782012-02-29 15:14:38 -0800249 if len(additional_args) > 0:
250 cmd.append('--')
251 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700252
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700253 # ThinLTO opens lots of files at the same time.
254 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700255 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
256 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700257 # If we were in interactive mode, ignore the exit code; it'll be whatever
258 # they last ran w/in the chroot and won't matter to us one way or another.
259 # Note this does allow chroot entrance to fail and be ignored during
260 # interactive; this is however a rare case and the user will immediately
261 # see it (nor will they be checking the exit code manually).
262 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700263 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800264
265
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600266def _ImageFileForChroot(chroot):
267 """Find the image file that should be associated with |chroot|.
268
269 This function does not check if the image exists; it simply returns the
270 filename that would be used.
271
272 Args:
273 chroot: Path to the chroot.
274
275 Returns:
276 Path to an image file that would be associated with chroot.
277 """
278 return chroot.rstrip('/') + '.img'
279
280
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600281def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
282 """Create a snapshot for the specified chroot VG/LV.
283
284 Args:
285 snapshot_name: The name of the new snapshot.
286 chroot_vg: The name of the VG containing the origin LV.
287 chroot_lv: The name of the origin LV.
288
289 Returns:
290 True if the snapshot was created, or False if a snapshot with the same
291 name already exists.
292
293 Raises:
294 SystemExit: The lvcreate command failed.
295 """
296 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
297 logging.error('Cannot create snapshot %s: A volume with that name already '
298 'exists.', snapshot_name)
299 return False
300
301 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
302 chroot_vg, chroot_lv)]
303 try:
304 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
305 chroot_lv, chroot_vg)
306 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
307 return True
308 except cros_build_lib.RunCommandError:
309 raise SystemExit('Running %r failed!' % cmd)
310
311
312def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
313 """Delete the named snapshot from the specified chroot VG.
314
315 If the requested snapshot is not found, nothing happens. The main chroot LV
316 and internal thinpool LV cannot be deleted with this function.
317
318 Args:
319 snapshot_name: The name of the snapshot to delete.
320 chroot_vg: The name of the VG containing the origin LV.
321 chroot_lv: The name of the origin LV.
322
323 Raises:
324 SystemExit: The lvremove command failed.
325 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600326 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
327 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600328 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
329 'if you want to remove the whole chroot.', snapshot_name)
330 return
331
332 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
333 return
334
335 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
336 try:
337 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
338 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
339 except cros_build_lib.RunCommandError:
340 raise SystemExit('Running %r failed!' % cmd)
341
342
343def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
344 """Restore the chroot to an existing snapshot.
345
346 This is done by renaming the original |chroot_lv| LV to a temporary name,
347 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
348 now unused LV. If an error occurs, attempts to rename the original snapshot
349 back to |chroot_lv| to leave the chroot unchanged.
350
351 The chroot must be unmounted before calling this function, and will be left
352 unmounted after this function returns.
353
354 Args:
355 snapshot_name: The name of the snapshot to restore. This snapshot will no
356 longer be accessible at its original name after this function finishes.
357 chroot_vg: The VG containing the chroot LV and snapshot LV.
358 chroot_lv: The name of the original chroot LV.
359
360 Returns:
361 True if the chroot was restored to the requested snapshot, or False if
362 the snapshot wasn't found or isn't valid.
363
364 Raises:
365 SystemExit: Any of the LVM commands failed.
366 """
367 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600368 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
369 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600370 snapshot_name not in valid_snapshots):
371 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
372 snapshot_name, ', '.join(valid_snapshots))
373 return False
374
375 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
376 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
377 try:
378 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
379 except cros_build_lib.RunCommandError:
380 raise SystemExit('Running %r failed!' % cmd)
381
382 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
383 try:
384 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
385 except cros_build_lib.RunCommandError:
386 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
387 try:
388 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
389 except cros_build_lib.RunCommandError:
390 raise SystemExit('Failed to rename %s to chroot and failed to restore '
391 '%s back to chroot. Failed command: %r' %
392 (snapshot_name, backup_chroot_name, cmd))
393 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
394 'been restored. Failed command: %r' %
395 (snapshot_name, cmd))
396
397 # Some versions of LVM set snapshots to be skipped at auto-activate time.
398 # Other versions don't have this flag at all. We run lvchange to try
399 # disabling auto-skip and activating the volume, but ignore errors. Versions
400 # that don't have the flag should be auto-activated.
401 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
402 cmd = ['lvchange', '-kn', chroot_lv_path]
403 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
404 error_code_ok=True)
405
406 # Activate the LV in case the lvchange above was needed. Activating an LV
407 # that is already active shouldn't do anything, so this is safe to run even if
408 # the -kn wasn't needed.
409 cmd = ['lvchange', '-ay', chroot_lv_path]
410 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
411
412 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
413 try:
414 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
415 except cros_build_lib.RunCommandError:
416 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
417 (chroot_vg, backup_chroot_name, cmd))
418
419 return True
420
421
422def ListChrootSnapshots(chroot_vg, chroot_lv):
423 """Return all snapshots in |chroot_vg| regardless of origin volume.
424
425 Args:
426 chroot_vg: The name of the VG containing the chroot.
427 chroot_lv: The name of the chroot LV.
428
429 Returns:
430 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
431
432 Raises:
433 SystemExit: The lvs command failed.
434 """
435 if not chroot_vg or not chroot_lv:
436 return []
437
438 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
439 '--noheadings', '--separator', '\t', chroot_vg]
440 try:
441 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
442 redirect_stdout=True)
443 except cros_build_lib.RunCommandError:
444 raise SystemExit('Running %r failed!' % cmd)
445
446 # Once the thin origin volume has been deleted, there's no way to tell a
447 # snapshot apart from any other volume. Since this VG is created and managed
448 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
449 # valid snapshots.
450 snapshots = []
451 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
452 for line in result.output.splitlines():
453 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
454 if (lv_name == chroot_lv or
Benjamin Gordon74645232018-05-04 17:40:42 -0600455 lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
456 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600457 not snapshot_attrs.match(lv_attr)):
458 continue
459 snapshots.append(lv_name)
460 return snapshots
461
462
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600463def _FindSubmounts(*args):
464 """Find all mounts matching each of the paths in |args| and any submounts.
465
466 Returns:
467 A list of all matching mounts in the order found in /proc/mounts.
468 """
469 mounts = []
470 paths = [p.rstrip('/') for p in args]
471 for mtab in osutils.IterateMountPoints():
472 for path in paths:
473 if mtab.destination == path or mtab.destination.startswith(path + '/'):
474 mounts.append(mtab.destination)
475 break
476
477 return mounts
478
479
David James56e6c2c2012-10-24 23:54:41 -0700480def _SudoCommand():
481 """Get the 'sudo' command, along with all needed environment variables."""
482
David James5a73b4d2013-03-07 10:23:40 -0800483 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
484 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700485 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800486 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700487 value = os.environ.get(key)
488 if value is not None:
489 cmd += ['%s=%s' % (key, value)]
490
491 # Pass in the path to the depot_tools so that users can access them from
492 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400493 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500494
David James56e6c2c2012-10-24 23:54:41 -0700495 return cmd
496
497
Josh Triplett472a4182013-03-08 11:48:57 -0800498def _ReportMissing(missing):
499 """Report missing utilities, then exit.
500
501 Args:
502 missing: List of missing utilities, as returned by
503 osutils.FindMissingBinaries. If non-empty, will not return.
504 """
505
506 if missing:
507 raise SystemExit(
508 'The tool(s) %s were not found.\n'
509 'Please install the appropriate package in your host.\n'
510 'Example(ubuntu):\n'
511 ' sudo apt-get install <packagename>'
512 % ', '.join(missing))
513
514
515def _ProxySimSetup(options):
516 """Set up proxy simulator, and return only in the child environment.
517
518 TODO: Ideally, this should support multiple concurrent invocations of
519 cros_sdk --proxy-sim; currently, such invocations will conflict with each
520 other due to the veth device names and IP addresses. Either this code would
521 need to generate fresh, unused names for all of these before forking, or it
522 would need to support multiple concurrent cros_sdk invocations sharing one
523 proxy and allowing it to exit when unused (without counting on any local
524 service-management infrastructure on the host).
525 """
526
527 may_need_mpm = False
528 apache_bin = osutils.Which('apache2')
529 if apache_bin is None:
530 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
531 if apache_bin is None:
532 _ReportMissing(('apache2',))
533 else:
534 may_need_mpm = True
535
536 # Module names and .so names included for ease of grepping.
537 apache_modules = [('proxy_module', 'mod_proxy.so'),
538 ('proxy_connect_module', 'mod_proxy_connect.so'),
539 ('proxy_http_module', 'mod_proxy_http.so'),
540 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
541
542 # Find the apache module directory, and make sure it has the modules we need.
543 module_dirs = {}
544 for g in PROXY_APACHE_MODULE_GLOBS:
545 for mod, so in apache_modules:
546 for f in glob.glob(os.path.join(g, so)):
547 module_dirs.setdefault(os.path.dirname(f), []).append(so)
548 for apache_module_path, modules_found in module_dirs.iteritems():
549 if len(modules_found) == len(apache_modules):
550 break
551 else:
552 # Appease cros lint, which doesn't understand that this else block will not
553 # fall through to the subsequent code which relies on apache_module_path.
554 apache_module_path = None
555 raise SystemExit(
556 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500557 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800558
559 def check_add_module(name):
560 so = 'mod_%s.so' % name
561 if os.access(os.path.join(apache_module_path, so), os.F_OK):
562 mod = '%s_module' % name
563 apache_modules.append((mod, so))
564 return True
565 return False
566
567 check_add_module('authz_core')
568 if may_need_mpm:
569 for mpm in PROXY_APACHE_MPMS:
570 if check_add_module('mpm_%s' % mpm):
571 break
572
573 veth_host = '%s-host' % PROXY_VETH_PREFIX
574 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
575
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500576 # Set up locks to sync the net namespace setup. We need the child to create
577 # the net ns first, and then have the parent assign the guest end of the veth
578 # interface to the child's new network namespace & bring up the proxy. Only
579 # then can the child move forward and rely on the network being up.
580 ns_create_lock = locking.PipeLock()
581 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800582
583 pid = os.fork()
584 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500585 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800586 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500587
588 # Signal the parent the ns is ready to be configured.
589 ns_create_lock.Post()
590 del ns_create_lock
591
592 # Wait for the parent to finish setting up the ns/proxy.
593 ns_setup_lock.Wait()
594 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800595
596 # Set up child side of the network.
597 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500598 ('ip', 'link', 'set', 'up', 'lo'),
599 ('ip', 'address', 'add',
600 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
601 'dev', veth_guest),
602 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800603 )
604 try:
605 for cmd in commands:
606 cros_build_lib.RunCommand(cmd, print_cmd=False)
607 except cros_build_lib.RunCommandError:
608 raise SystemExit('Running %r failed!' % (cmd,))
609
610 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
611 for proto in ('http', 'https', 'ftp'):
612 os.environ[proto + '_proxy'] = proxy_url
613 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
614 os.environ.pop(v, None)
615 return
616
Josh Triplett472a4182013-03-08 11:48:57 -0800617 # Set up parent side of the network.
618 uid = int(os.environ.get('SUDO_UID', '0'))
619 gid = int(os.environ.get('SUDO_GID', '0'))
620 if uid == 0 or gid == 0:
621 for username in PROXY_APACHE_FALLBACK_USERS:
622 try:
623 pwnam = pwd.getpwnam(username)
624 uid, gid = pwnam.pw_uid, pwnam.pw_gid
625 break
626 except KeyError:
627 continue
628 if uid == 0 or gid == 0:
629 raise SystemExit('Could not find a non-root user to run Apache as')
630
631 chroot_parent, chroot_base = os.path.split(options.chroot)
632 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
633 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
634
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500635 # Wait for the child to create the net ns.
636 ns_create_lock.Wait()
637 del ns_create_lock
638
Josh Triplett472a4182013-03-08 11:48:57 -0800639 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500640 'User #%u' % uid,
641 'Group #%u' % gid,
642 'PidFile %s' % pid_file,
643 'ErrorLog %s' % log_file,
644 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
645 'ServerName %s' % PROXY_HOST_IP,
646 'ProxyRequests On',
647 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800648 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500649 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
650 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800651 ]
652 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500653 ('ip', 'link', 'add', 'name', veth_host,
654 'type', 'veth', 'peer', 'name', veth_guest),
655 ('ip', 'address', 'add',
656 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
657 'dev', veth_host),
658 ('ip', 'link', 'set', veth_host, 'up'),
659 ([apache_bin, '-f', '/dev/null'] +
660 [arg for d in apache_directives for arg in ('-C', d)]),
661 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800662 )
663 cmd = None # Make cros lint happy.
664 try:
665 for cmd in commands:
666 cros_build_lib.RunCommand(cmd, print_cmd=False)
667 except cros_build_lib.RunCommandError:
668 # Clean up existing interfaces, if any.
669 cmd_cleanup = ('ip', 'link', 'del', veth_host)
670 try:
671 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
672 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700673 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800674 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500675
676 # Signal the child that the net ns/proxy is fully configured now.
677 ns_setup_lock.Post()
678 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800679
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400680 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800681
682
Mike Frysingera78a56e2012-11-20 06:02:30 -0500683def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700684 """Re-execute cros_sdk as root.
685
686 Also unshare the mount namespace so as to ensure that processes outside
687 the chroot can't mess with our mounts.
688 """
689 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500690 cmd = _SudoCommand() + ['--'] + argv
691 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500692 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400693 # We must set up the cgroups mounts before we enter our own namespace.
694 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800695 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700696
697
Mike Frysinger34db8692013-11-11 14:54:08 -0500698def _CreateParser(sdk_latest_version, bootstrap_latest_version):
699 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400700 usage = ('usage: %(prog)s [options] '
701 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
702 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
703 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700704
Mike Frysinger34db8692013-11-11 14:54:08 -0500705 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500706 default_chroot = os.path.join(constants.SOURCE_ROOT,
707 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400708 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700709 '--chroot', dest='chroot', default=default_chroot, type='path',
710 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600711 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
712 default=True,
713 help='Do not mount the chroot on a loopback image; '
714 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800715
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400716 parser.add_argument('--chrome_root', type='path',
717 help='Mount this chrome root into the SDK chroot')
718 parser.add_argument('--chrome_root_mount', type='path',
719 help='Mount chrome into this path inside SDK chroot')
720 parser.add_argument('--nousepkg', action='store_true', default=False,
721 help='Do not use binary packages when creating a chroot.')
722 parser.add_argument('-u', '--url', dest='sdk_url',
723 help='Use sdk tarball located at this url. Use file:// '
724 'for local files.')
725 parser.add_argument('--sdk-version',
726 help=('Use this sdk version. For prebuilt, current is %r'
727 ', for bootstrapping it is %r.'
728 % (sdk_latest_version, bootstrap_latest_version)))
729 parser.add_argument('--workspace',
730 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900731 parser.add_argument('--goma_dir', type='path',
732 help='Goma installed directory to mount into the chroot.')
733 parser.add_argument('--goma_client_json', type='path',
734 help='Service account json file to use goma on bot. '
735 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800736
737 # Use type=str instead of type='path' to prevent the given path from being
738 # transfered to absolute path automatically.
739 parser.add_argument('--working-dir', type=str,
740 help='Run the command in specific working directory in '
741 'chroot. If the given directory is a relative '
742 'path, this program will transfer the path to '
743 'the corresponding one inside chroot.')
744
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400745 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500746
747 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400748 group = parser.add_argument_group('Commands')
749 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500750 '--enter', action='store_true', default=False,
751 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400752 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500753 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500754 help='Create the chroot only if it does not already exist. '
755 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400756 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500757 '--bootstrap', action='store_true', default=False,
758 help='Build everything from scratch, including the sdk. '
759 'Use this only if you need to validate a change '
760 'that affects SDK creation itself (toolchain and '
761 'build are typically the only folk who need this). '
762 'Note this will quite heavily slow down the build. '
763 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400764 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500765 '-r', '--replace', action='store_true', default=False,
766 help='Replace an existing SDK chroot. Basically an alias '
767 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400768 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500769 '--delete', action='store_true', default=False,
770 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400771 group.add_argument(
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600772 '--unmount', action='store_true', default=False,
773 help='Unmount and clean up devices associated with the '
774 'SDK chroot if it exists. This does not delete the '
775 'backing image file, so the same chroot can be later '
776 're-mounted for reuse. To fully delete the chroot, use '
777 '--delete. This is primarily useful for working on '
778 'cros_sdk or the chroot setup; you should not need it '
779 'under normal circumstances.')
780 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500781 '--download', action='store_true', default=False,
782 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600783 group.add_argument(
784 '--snapshot-create', metavar='SNAPSHOT_NAME',
785 help='Create a snapshot of the chroot. Requires that the chroot was '
786 'created without the --nouse-image option.')
787 group.add_argument(
788 '--snapshot-restore', metavar='SNAPSHOT_NAME',
789 help='Restore the chroot to a previously created snapshot.')
790 group.add_argument(
791 '--snapshot-delete', metavar='SNAPSHOT_NAME',
792 help='Delete a previously created snapshot. Deleting a snapshot that '
793 'does not exist is not an error.')
794 group.add_argument(
795 '--snapshot-list', action='store_true', default=False,
796 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500797 commands = group
798
Mike Frysinger80dfce92014-04-21 10:58:53 -0400799 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400800 group = parser.add_argument_group('Namespaces')
801 group.add_argument('--proxy-sim', action='store_true', default=False,
802 help='Simulate a restrictive network requiring an outbound'
803 ' proxy.')
804 group.add_argument('--no-ns-pid', dest='ns_pid',
805 default=True, action='store_false',
806 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400807
Mike Frysinger34db8692013-11-11 14:54:08 -0500808 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400809 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500810 'Internal Chromium OS Build Team Options',
811 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400812 group.add_argument('--buildbot-log-version', default=False,
813 action='store_true',
814 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500815
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400816 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500817
818
819def main(argv):
820 conf = cros_build_lib.LoadKeyValueFile(
821 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
822 ignore_missing=True)
823 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
824 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
825 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400826 options = parser.parse_args(argv)
827 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800828
829 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500830 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800831
Brian Harring1790ac42012-09-23 08:53:33 -0700832 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700833 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600834 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700835 "cros_sdk is currently only supported on x86_64; you're running"
836 " %s. Please find a x86_64 machine." % (host,))
837
Josh Triplett472a4182013-03-08 11:48:57 -0800838 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
839 if options.proxy_sim:
840 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600841 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800842
Benjamin Gordon040a1162017-06-29 13:44:47 -0600843 if (sdk_latest_version == '<unknown>' or
844 bootstrap_latest_version == '<unknown>'):
845 cros_build_lib.Die(
846 'No SDK version was found. '
847 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
848 'Please change to a directory inside your Chromium OS source tree\n'
849 'and retry. If you need to setup a Chromium OS source tree, see\n'
850 ' http://www.chromium.org/chromium-os/developer-guide')
851
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600852 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
853 or options.snapshot_delete or options.snapshot_list)
854 if any_snapshot_operation and not options.use_image:
855 cros_build_lib.Die('Snapshot operations are not compatible with '
856 '--nouse-image.')
857
858 if (options.snapshot_delete and options.snapshot_delete ==
859 options.snapshot_restore):
860 parser.error('Cannot --snapshot_delete the same snapshot you are '
861 'restoring with --snapshot_restore.')
862
David James471532c2013-01-21 10:23:31 -0800863 _ReExecuteIfNeeded([sys.argv[0]] + argv)
864
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600865 lock_path = os.path.dirname(options.chroot)
866 lock_path = os.path.join(
867 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
868
Brian Harring218e13c2012-10-10 16:21:26 -0700869 # Expand out the aliases...
870 if options.replace:
871 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800872
Brian Harring218e13c2012-10-10 16:21:26 -0700873 if options.bootstrap:
874 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800875
Brian Harring218e13c2012-10-10 16:21:26 -0700876 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400877 # pylint: disable=protected-access
878 # This _group_actions access sucks, but upstream decided to not include an
879 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700880 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400881 for x in commands._group_actions)
882 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700883 options.enter |= bool(chroot_command)
884
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600885 if (options.delete and not options.create and
886 (options.enter or any_snapshot_operation)):
887 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700888 "was specified makes no sense.")
889
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600890 if (options.unmount and
891 (options.create or options.enter or any_snapshot_operation)):
892 parser.error('--unmount cannot be specified with other chroot actions.')
893
Yong Hong84ba9172018-02-07 01:37:42 +0800894 if options.working_dir is not None and not os.path.isabs(options.working_dir):
895 options.working_dir = path_util.ToChrootPath(options.working_dir)
896
Benjamin Gordon35194f12017-07-19 10:26:22 -0600897 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600898 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600899 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600900 not options.unmount and not missing_image_tools and
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600901 os.path.exists(_ImageFileForChroot(options.chroot))):
902 # Try to re-mount an existing image in case the user has rebooted.
903 with cgroups.SimpleContainChildren('cros_sdk'):
904 with locking.FileLock(lock_path, 'chroot lock') as lock:
905 logging.debug('Checking if existing chroot image can be mounted.')
906 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600907 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600908 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600909 if chroot_exists:
910 logging.notice('Mounted existing image %s on chroot',
911 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700912
913 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600914 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700915 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800916
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600917 # Make sure we will download if we plan to create.
918 options.download |= options.create
919
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600920 # Anything that needs to manipulate the main chroot mount or communicate with
921 # LVM needs to be done here before we enter the new namespaces.
922
923 # If deleting, do it regardless of the use_image flag so that a
924 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600925 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
926 # favor of this block.
927 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600928 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600929 with cgroups.SimpleContainChildren('cros_sdk'):
930 with locking.FileLock(lock_path, 'chroot lock') as lock:
931 lock.write_lock()
932 if missing_image_tools:
933 logging.notice('Unmounting chroot.')
934 osutils.UmountTree(options.chroot)
935 else:
936 logging.notice('Deleting chroot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600937 cros_sdk_lib.CleanupChrootMount(options.chroot, delete_image=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600938 osutils.RmDir(options.chroot, ignore_missing=True)
939 chroot_deleted = True
940
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600941 # If cleanup was requested, we have to do it while we're still in the original
942 # namespace. Since cleaning up the mount will interfere with any other
943 # commands, we exit here. The check above should have made sure that no other
944 # action was requested, anyway.
945 if options.unmount:
946 with locking.FileLock(lock_path, 'chroot lock') as lock:
947 lock.write_lock()
948 CleanupChroot(options.chroot)
949 sys.exit(0)
950
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600951 # Make sure the main chroot mount is visible. Contents will be filled in
952 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600953 if options.create and options.use_image:
954 if missing_image_tools:
955 raise SystemExit(
956 '''The tool(s) %s were not found.
957Please make sure the lvm2 and thin-provisioning-tools packages
958are installed on your host.
959Example(ubuntu):
960 sudo apt-get install lvm2 thin-provisioning-tools
961
962If you want to run without lvm2, pass --nouse-image (chroot
963snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600964
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600965 logging.debug('Making sure chroot image is mounted.')
966 with cgroups.SimpleContainChildren('cros_sdk'):
967 with locking.FileLock(lock_path, 'chroot lock') as lock:
968 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600969 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600970 cros_build_lib.Die('Unable to mount %s on chroot',
971 _ImageFileForChroot(options.chroot))
972 logging.notice('Mounted %s on chroot',
973 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600974
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600975 # Snapshot operations will always need the VG/LV, but other actions won't.
976 if any_snapshot_operation:
977 with cgroups.SimpleContainChildren('cros_sdk'):
978 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -0600979 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600980 options.chroot)
981 if not chroot_vg or not chroot_lv:
982 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
983 options.chroot)
984
985 # Delete snapshot before creating a new one. This allows the user to
986 # throw out old state, create a new snapshot, and enter the chroot in a
987 # single call to cros_sdk. Since restore involves deleting, also do it
988 # before creating.
989 if options.snapshot_restore:
990 lock.write_lock()
991 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
992 if options.snapshot_restore not in valid_snapshots:
993 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
994 'Valid snapshots: %s', options.snapshot_restore,
995 ', '.join(valid_snapshots))
996 osutils.UmountTree(options.chroot)
997 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
998 chroot_lv):
999 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -06001000 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001001 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
1002
1003 # Use a read lock for snapshot delete and create even though they modify
1004 # the filesystem, because they don't modify the mounted chroot itself.
1005 # The underlying LVM commands take their own locks, so conflicting
1006 # concurrent operations here may crash cros_sdk, but won't corrupt the
1007 # chroot image. This tradeoff seems worth it to allow snapshot
1008 # operations on chroots that have a process inside.
1009 if options.snapshot_delete:
1010 lock.read_lock()
1011 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1012
1013 if options.snapshot_create:
1014 lock.read_lock()
1015 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1016 chroot_lv):
1017 cros_build_lib.Die('Unable to create snapshot.')
1018
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001019 img_path = _ImageFileForChroot(options.chroot)
1020 if (options.use_image and os.path.exists(options.chroot) and
1021 os.path.exists(img_path)):
1022 img_stat = os.stat(img_path)
1023 img_used_bytes = img_stat.st_blocks * 512
1024
1025 mount_stat = os.statvfs(options.chroot)
1026 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1027 mount_stat.f_bfree)
1028
1029 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1030 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1031 logging.notice('%s is using %s GiB more than needed. Running '
1032 'fstrim.', img_path, extra_gbs)
1033 cmd = ['fstrim', options.chroot]
1034 try:
1035 cros_build_lib.RunCommand(cmd, print_cmd=False)
1036 except cros_build_lib.RunCommandError as e:
1037 logging.warning('Running fstrim failed. Consider running fstrim on '
1038 'your chroot manually.\nError: %s', e)
1039
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001040 # Enter a new set of namespaces. Everything after here cannot directly affect
1041 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001042 namespaces.SimpleUnshare()
1043 if options.ns_pid:
1044 first_pid = namespaces.CreatePidNs()
1045 else:
1046 first_pid = None
1047
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001048 if options.snapshot_list:
1049 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1050 print(snap)
1051 sys.exit(0)
1052
Brian Harringb938c782012-02-29 15:14:38 -08001053 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001054 sdk_version = (bootstrap_latest_version if options.bootstrap
1055 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001056 else:
1057 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001058 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001059 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001060
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001061 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001062 if options.download:
1063 if options.sdk_url:
1064 urls = [options.sdk_url]
1065 elif options.bootstrap:
1066 urls = GetStage3Urls(sdk_version)
1067 else:
1068 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001069
Mike Frysinger80dfce92014-04-21 10:58:53 -04001070 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001071 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001072 if options.proxy_sim:
1073 _ProxySimSetup(options)
1074
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001075 if (options.delete and not chroot_deleted and
1076 (os.path.exists(options.chroot) or
1077 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001078 lock.write_lock()
1079 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001080
David James56e6c2c2012-10-24 23:54:41 -07001081 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1082 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001083 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001084
David James56e6c2c2012-10-24 23:54:41 -07001085 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001086 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001087 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001088 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001089 continue
1090 lock.write_lock(
1091 "Upgrade to %r needed but chroot is locked; please exit "
1092 "all instances so this upgrade can finish." % src)
1093 if not os.path.exists(src):
1094 # Note that while waiting for the write lock, src may've vanished;
1095 # it's a rare race during the upgrade process that's a byproduct
1096 # of us avoiding taking a write lock to do the src check. If we
1097 # took a write lock for that check, it would effectively limit
1098 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001099 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001100 elif not os.path.exists(target):
1101 # Upgrade occurred, but a reversion, or something whacky
1102 # occurred writing to the old location. Wipe and continue.
1103 os.rename(src, target)
1104 else:
1105 # Upgrade occurred once already, but either a reversion or
1106 # some before/after separate cros_sdk usage is at play.
1107 # Wipe and continue.
1108 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001109
David James56e6c2c2012-10-24 23:54:41 -07001110 if options.download:
1111 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001112 sdk_tarball = FetchRemoteTarballs(
1113 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001114
David James56e6c2c2012-10-24 23:54:41 -07001115 if options.create:
1116 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001117 # Recheck if the chroot is set up here before creating to make sure we
1118 # account for whatever the various delete/unmount/remount steps above
1119 # have done.
1120 if cros_sdk_lib.IsChrootReady(options.chroot):
1121 logging.debug('Chroot already exists. Skipping creation.')
1122 else:
1123 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
1124 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001125
David James56e6c2c2012-10-24 23:54:41 -07001126 if options.enter:
1127 lock.read_lock()
1128 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001129 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001130 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001131 options.working_dir, chroot_command)