blob: 3cb50e14fa35c5a0d370bb66ef45bd7429543d2f [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.
Brian Harring1790ac42012-09-23 08:53:33 -0700118 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700119 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700120 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Mike Frysinger27e21b72018-07-12 14:20:21 -0400121 # pylint: disable=undefined-loop-variable
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200122 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700123 parsed = urlparse.urlparse(url)
124 tarball_name = os.path.basename(parsed.path)
125 if parsed.scheme in ('', 'file'):
126 if os.path.exists(parsed.path):
127 return parsed.path
128 continue
129 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700130 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800131 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900132 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
133 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700134 successful = False
135 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700136 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700137 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700138 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700139 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500140 elif header.lower().startswith('content-length:'):
141 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700142 if successful:
143 break
144 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200145 break
146 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700147 if allow_none:
148 return None
149 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200150
Brian Harringae0a5322012-09-15 01:46:51 -0700151 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700152 current_size = 0
153 if os.path.exists(tarball_dest):
154 current_size = os.path.getsize(tarball_dest)
155 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700156 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700157 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100158
Brian Harring1790ac42012-09-23 08:53:33 -0700159 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800160 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900161 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
162 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800163
Brian Harring1790ac42012-09-23 08:53:33 -0700164 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700165 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
166 # because we might have partial overlap between known prefixes.
167 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
168 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
169 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700170 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700171 if (filename == tarball_name or
172 any([(filename.startswith(p) and
173 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
174 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700175 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700176 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700177 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200178
Brian Harringb938c782012-02-29 15:14:38 -0800179 return tarball_dest
180
181
Benjamin Gordon589873b2018-05-31 14:30:56 -0600182def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600183 """Creates a new chroot from a given SDK.
184
185 Args:
186 chroot_path: Path where the new chroot will be created.
187 sdk_tarball: Path to a downloaded Gentoo Stage3 or Chromium OS SDK tarball.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600188 cache_dir: Path to a directory that will be used for caching portage files,
189 etc.
190 nousepkg: If True, pass --nousepkg to cros_setup_toolchains inside the
191 chroot.
192 """
Brian Harringb938c782012-02-29 15:14:38 -0800193
Brian Harring1790ac42012-09-23 08:53:33 -0700194 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700195 '--chroot', chroot_path,
196 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700197
Mike Frysinger2de7f042012-07-10 04:45:03 -0400198 if nousepkg:
199 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800200
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700201 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800202 try:
203 cros_build_lib.RunCommand(cmd, print_cmd=False)
204 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700205 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800206
207
208def DeleteChroot(chroot_path):
209 """Deletes an existing chroot"""
210 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
211 '--delete']
212 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700213 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800214 cros_build_lib.RunCommand(cmd, print_cmd=False)
215 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700216 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800217
218
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600219def CleanupChroot(chroot_path):
220 """Unmounts a chroot and cleans up any associated devices."""
Don Garrett36650112018-06-28 15:54:34 -0700221 cros_sdk_lib.CleanupChrootMount(chroot_path, delete=False)
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600222
223
Brian Harringae0a5322012-09-15 01:46:51 -0700224def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Mike Frysinger0b2d9ee2019-02-28 17:05:47 -0500225 goma_dir, goma_client_json, working_dir, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800226 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400227 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
228 # The os.ST_NOSUID constant wasn't added until python-3.2.
229 if st.f_flag & 0x2:
230 cros_build_lib.Die('chroot cannot be in a nosuid mount')
231
Brian Harringae0a5322012-09-15 01:46:51 -0700232 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800233 if chrome_root:
234 cmd.extend(['--chrome_root', chrome_root])
235 if chrome_root_mount:
236 cmd.extend(['--chrome_root_mount', chrome_root_mount])
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
David James56e6c2c2012-10-24 23:54:41 -0700458def _SudoCommand():
459 """Get the 'sudo' command, along with all needed environment variables."""
460
David James5a73b4d2013-03-07 10:23:40 -0800461 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
462 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700463 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800464 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700465 value = os.environ.get(key)
466 if value is not None:
467 cmd += ['%s=%s' % (key, value)]
468
469 # Pass in the path to the depot_tools so that users can access them from
470 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400471 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500472
David James56e6c2c2012-10-24 23:54:41 -0700473 return cmd
474
475
Josh Triplett472a4182013-03-08 11:48:57 -0800476def _ReportMissing(missing):
477 """Report missing utilities, then exit.
478
479 Args:
480 missing: List of missing utilities, as returned by
481 osutils.FindMissingBinaries. If non-empty, will not return.
482 """
483
484 if missing:
485 raise SystemExit(
486 'The tool(s) %s were not found.\n'
487 'Please install the appropriate package in your host.\n'
488 'Example(ubuntu):\n'
489 ' sudo apt-get install <packagename>'
490 % ', '.join(missing))
491
492
493def _ProxySimSetup(options):
494 """Set up proxy simulator, and return only in the child environment.
495
496 TODO: Ideally, this should support multiple concurrent invocations of
497 cros_sdk --proxy-sim; currently, such invocations will conflict with each
498 other due to the veth device names and IP addresses. Either this code would
499 need to generate fresh, unused names for all of these before forking, or it
500 would need to support multiple concurrent cros_sdk invocations sharing one
501 proxy and allowing it to exit when unused (without counting on any local
502 service-management infrastructure on the host).
503 """
504
505 may_need_mpm = False
506 apache_bin = osutils.Which('apache2')
507 if apache_bin is None:
508 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
509 if apache_bin is None:
510 _ReportMissing(('apache2',))
511 else:
512 may_need_mpm = True
513
514 # Module names and .so names included for ease of grepping.
515 apache_modules = [('proxy_module', 'mod_proxy.so'),
516 ('proxy_connect_module', 'mod_proxy_connect.so'),
517 ('proxy_http_module', 'mod_proxy_http.so'),
518 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
519
520 # Find the apache module directory, and make sure it has the modules we need.
521 module_dirs = {}
522 for g in PROXY_APACHE_MODULE_GLOBS:
523 for mod, so in apache_modules:
524 for f in glob.glob(os.path.join(g, so)):
525 module_dirs.setdefault(os.path.dirname(f), []).append(so)
526 for apache_module_path, modules_found in module_dirs.iteritems():
527 if len(modules_found) == len(apache_modules):
528 break
529 else:
530 # Appease cros lint, which doesn't understand that this else block will not
531 # fall through to the subsequent code which relies on apache_module_path.
532 apache_module_path = None
533 raise SystemExit(
534 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500535 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800536
537 def check_add_module(name):
538 so = 'mod_%s.so' % name
539 if os.access(os.path.join(apache_module_path, so), os.F_OK):
540 mod = '%s_module' % name
541 apache_modules.append((mod, so))
542 return True
543 return False
544
545 check_add_module('authz_core')
546 if may_need_mpm:
547 for mpm in PROXY_APACHE_MPMS:
548 if check_add_module('mpm_%s' % mpm):
549 break
550
551 veth_host = '%s-host' % PROXY_VETH_PREFIX
552 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
553
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500554 # Set up locks to sync the net namespace setup. We need the child to create
555 # the net ns first, and then have the parent assign the guest end of the veth
556 # interface to the child's new network namespace & bring up the proxy. Only
557 # then can the child move forward and rely on the network being up.
558 ns_create_lock = locking.PipeLock()
559 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800560
561 pid = os.fork()
562 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500563 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800564 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500565
566 # Signal the parent the ns is ready to be configured.
567 ns_create_lock.Post()
568 del ns_create_lock
569
570 # Wait for the parent to finish setting up the ns/proxy.
571 ns_setup_lock.Wait()
572 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800573
574 # Set up child side of the network.
575 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500576 ('ip', 'link', 'set', 'up', 'lo'),
577 ('ip', 'address', 'add',
578 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
579 'dev', veth_guest),
580 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800581 )
582 try:
583 for cmd in commands:
584 cros_build_lib.RunCommand(cmd, print_cmd=False)
585 except cros_build_lib.RunCommandError:
586 raise SystemExit('Running %r failed!' % (cmd,))
587
588 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
589 for proto in ('http', 'https', 'ftp'):
590 os.environ[proto + '_proxy'] = proxy_url
591 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
592 os.environ.pop(v, None)
593 return
594
Josh Triplett472a4182013-03-08 11:48:57 -0800595 # Set up parent side of the network.
596 uid = int(os.environ.get('SUDO_UID', '0'))
597 gid = int(os.environ.get('SUDO_GID', '0'))
598 if uid == 0 or gid == 0:
599 for username in PROXY_APACHE_FALLBACK_USERS:
600 try:
601 pwnam = pwd.getpwnam(username)
602 uid, gid = pwnam.pw_uid, pwnam.pw_gid
603 break
604 except KeyError:
605 continue
606 if uid == 0 or gid == 0:
607 raise SystemExit('Could not find a non-root user to run Apache as')
608
609 chroot_parent, chroot_base = os.path.split(options.chroot)
610 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
611 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
612
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500613 # Wait for the child to create the net ns.
614 ns_create_lock.Wait()
615 del ns_create_lock
616
Josh Triplett472a4182013-03-08 11:48:57 -0800617 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500618 'User #%u' % uid,
619 'Group #%u' % gid,
620 'PidFile %s' % pid_file,
621 'ErrorLog %s' % log_file,
622 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
623 'ServerName %s' % PROXY_HOST_IP,
624 'ProxyRequests On',
625 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800626 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500627 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
628 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800629 ]
630 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500631 ('ip', 'link', 'add', 'name', veth_host,
632 'type', 'veth', 'peer', 'name', veth_guest),
633 ('ip', 'address', 'add',
634 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
635 'dev', veth_host),
636 ('ip', 'link', 'set', veth_host, 'up'),
637 ([apache_bin, '-f', '/dev/null'] +
638 [arg for d in apache_directives for arg in ('-C', d)]),
639 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800640 )
641 cmd = None # Make cros lint happy.
642 try:
643 for cmd in commands:
644 cros_build_lib.RunCommand(cmd, print_cmd=False)
645 except cros_build_lib.RunCommandError:
646 # Clean up existing interfaces, if any.
647 cmd_cleanup = ('ip', 'link', 'del', veth_host)
648 try:
649 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
650 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700651 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800652 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500653
654 # Signal the child that the net ns/proxy is fully configured now.
655 ns_setup_lock.Post()
656 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800657
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400658 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800659
660
Mike Frysingera78a56e2012-11-20 06:02:30 -0500661def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700662 """Re-execute cros_sdk as root.
663
664 Also unshare the mount namespace so as to ensure that processes outside
665 the chroot can't mess with our mounts.
666 """
667 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500668 cmd = _SudoCommand() + ['--'] + argv
669 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500670 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400671 # We must set up the cgroups mounts before we enter our own namespace.
672 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800673 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700674
675
Mike Frysinger34db8692013-11-11 14:54:08 -0500676def _CreateParser(sdk_latest_version, bootstrap_latest_version):
677 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400678 usage = ('usage: %(prog)s [options] '
679 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
680 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
681 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700682
Mike Frysinger34db8692013-11-11 14:54:08 -0500683 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500684 default_chroot = os.path.join(constants.SOURCE_ROOT,
685 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400686 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700687 '--chroot', dest='chroot', default=default_chroot, type='path',
688 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600689 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
690 default=True,
691 help='Do not mount the chroot on a loopback image; '
692 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800693
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400694 parser.add_argument('--chrome_root', type='path',
695 help='Mount this chrome root into the SDK chroot')
696 parser.add_argument('--chrome_root_mount', type='path',
697 help='Mount chrome into this path inside SDK chroot')
698 parser.add_argument('--nousepkg', action='store_true', default=False,
699 help='Do not use binary packages when creating a chroot.')
700 parser.add_argument('-u', '--url', dest='sdk_url',
701 help='Use sdk tarball located at this url. Use file:// '
702 'for local files.')
Manoj Guptaa0cf99f2019-04-15 20:14:35 -0700703 parser.add_argument('--self-bootstrap', dest='self_bootstrap', action='store_true',
704 default=False,
705 help=('Use previously build sdk for bootstrapping.'))
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400706 parser.add_argument('--sdk-version',
707 help=('Use this sdk version. For prebuilt, current is %r'
708 ', for bootstrapping it is %r.'
709 % (sdk_latest_version, bootstrap_latest_version)))
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900710 parser.add_argument('--goma_dir', type='path',
711 help='Goma installed directory to mount into the chroot.')
712 parser.add_argument('--goma_client_json', type='path',
713 help='Service account json file to use goma on bot. '
714 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800715
716 # Use type=str instead of type='path' to prevent the given path from being
717 # transfered to absolute path automatically.
718 parser.add_argument('--working-dir', type=str,
719 help='Run the command in specific working directory in '
720 'chroot. If the given directory is a relative '
721 'path, this program will transfer the path to '
722 'the corresponding one inside chroot.')
723
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400724 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500725
726 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400727 group = parser.add_argument_group('Commands')
728 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500729 '--enter', action='store_true', default=False,
730 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400731 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500732 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500733 help='Create the chroot only if it does not already exist. '
734 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400735 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500736 '--bootstrap', action='store_true', default=False,
737 help='Build everything from scratch, including the sdk. '
738 'Use this only if you need to validate a change '
739 'that affects SDK creation itself (toolchain and '
740 'build are typically the only folk who need this). '
741 'Note this will quite heavily slow down the build. '
742 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400743 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500744 '-r', '--replace', action='store_true', default=False,
745 help='Replace an existing SDK chroot. Basically an alias '
746 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400747 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500748 '--delete', action='store_true', default=False,
749 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400750 group.add_argument(
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600751 '--unmount', action='store_true', default=False,
752 help='Unmount and clean up devices associated with the '
753 'SDK chroot if it exists. This does not delete the '
754 'backing image file, so the same chroot can be later '
755 're-mounted for reuse. To fully delete the chroot, use '
756 '--delete. This is primarily useful for working on '
757 'cros_sdk or the chroot setup; you should not need it '
758 'under normal circumstances.')
759 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500760 '--download', action='store_true', default=False,
761 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600762 group.add_argument(
763 '--snapshot-create', metavar='SNAPSHOT_NAME',
764 help='Create a snapshot of the chroot. Requires that the chroot was '
765 'created without the --nouse-image option.')
766 group.add_argument(
767 '--snapshot-restore', metavar='SNAPSHOT_NAME',
768 help='Restore the chroot to a previously created snapshot.')
769 group.add_argument(
770 '--snapshot-delete', metavar='SNAPSHOT_NAME',
771 help='Delete a previously created snapshot. Deleting a snapshot that '
772 'does not exist is not an error.')
773 group.add_argument(
774 '--snapshot-list', action='store_true', default=False,
775 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500776 commands = group
777
Mike Frysinger80dfce92014-04-21 10:58:53 -0400778 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400779 group = parser.add_argument_group('Namespaces')
780 group.add_argument('--proxy-sim', action='store_true', default=False,
781 help='Simulate a restrictive network requiring an outbound'
782 ' proxy.')
783 group.add_argument('--no-ns-pid', dest='ns_pid',
784 default=True, action='store_false',
785 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400786
Mike Frysinger34db8692013-11-11 14:54:08 -0500787 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400788 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500789 'Internal Chromium OS Build Team Options',
790 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400791 group.add_argument('--buildbot-log-version', default=False,
792 action='store_true',
793 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500794
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400795 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500796
797
798def main(argv):
799 conf = cros_build_lib.LoadKeyValueFile(
800 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
801 ignore_missing=True)
802 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
803 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
804 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400805 options = parser.parse_args(argv)
806 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800807
808 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500809 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800810
Brian Harring1790ac42012-09-23 08:53:33 -0700811 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700812 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600813 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700814 "cros_sdk is currently only supported on x86_64; you're running"
815 " %s. Please find a x86_64 machine." % (host,))
816
Josh Triplett472a4182013-03-08 11:48:57 -0800817 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
818 if options.proxy_sim:
819 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600820 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800821
Manoj Guptaa0cf99f2019-04-15 20:14:35 -0700822 # Use latest SDK for bootstrapping if requested.
823 if options.self_bootstrap:
824 bootstrap_latest_version = sdk_latest_version
825
Benjamin Gordon040a1162017-06-29 13:44:47 -0600826 if (sdk_latest_version == '<unknown>' or
827 bootstrap_latest_version == '<unknown>'):
828 cros_build_lib.Die(
829 'No SDK version was found. '
830 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
831 'Please change to a directory inside your Chromium OS source tree\n'
832 'and retry. If you need to setup a Chromium OS source tree, see\n'
Mike Frysingerdcad4e02018-08-03 16:20:02 -0400833 ' https://dev.chromium.org/chromium-os/developer-guide')
Benjamin Gordon040a1162017-06-29 13:44:47 -0600834
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600835 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
836 or options.snapshot_delete or options.snapshot_list)
837 if any_snapshot_operation and not options.use_image:
838 cros_build_lib.Die('Snapshot operations are not compatible with '
839 '--nouse-image.')
840
841 if (options.snapshot_delete and options.snapshot_delete ==
842 options.snapshot_restore):
843 parser.error('Cannot --snapshot_delete the same snapshot you are '
844 'restoring with --snapshot_restore.')
845
David James471532c2013-01-21 10:23:31 -0800846 _ReExecuteIfNeeded([sys.argv[0]] + argv)
847
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600848 lock_path = os.path.dirname(options.chroot)
849 lock_path = os.path.join(
850 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
851
Brian Harring218e13c2012-10-10 16:21:26 -0700852 # Expand out the aliases...
853 if options.replace:
854 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800855
Brian Harring218e13c2012-10-10 16:21:26 -0700856 if options.bootstrap:
857 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800858
Brian Harring218e13c2012-10-10 16:21:26 -0700859 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400860 # pylint: disable=protected-access
861 # This _group_actions access sucks, but upstream decided to not include an
862 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700863 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400864 for x in commands._group_actions)
865 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700866 options.enter |= bool(chroot_command)
867
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600868 if (options.delete and not options.create and
869 (options.enter or any_snapshot_operation)):
870 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700871 "was specified makes no sense.")
872
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600873 if (options.unmount and
874 (options.create or options.enter or any_snapshot_operation)):
875 parser.error('--unmount cannot be specified with other chroot actions.')
876
Yong Hong84ba9172018-02-07 01:37:42 +0800877 if options.working_dir is not None and not os.path.isabs(options.working_dir):
878 options.working_dir = path_util.ToChrootPath(options.working_dir)
879
Benjamin Gordon35194f12017-07-19 10:26:22 -0600880 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600881 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600882 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600883 not options.unmount and not missing_image_tools and
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600884 os.path.exists(_ImageFileForChroot(options.chroot))):
885 # Try to re-mount an existing image in case the user has rebooted.
886 with cgroups.SimpleContainChildren('cros_sdk'):
887 with locking.FileLock(lock_path, 'chroot lock') as lock:
888 logging.debug('Checking if existing chroot image can be mounted.')
889 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600890 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600891 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600892 if chroot_exists:
893 logging.notice('Mounted existing image %s on chroot',
894 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700895
896 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600897 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700898 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800899
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600900 # Make sure we will download if we plan to create.
901 options.download |= options.create
902
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600903 # Anything that needs to manipulate the main chroot mount or communicate with
904 # LVM needs to be done here before we enter the new namespaces.
905
906 # If deleting, do it regardless of the use_image flag so that a
907 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600908 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
909 # favor of this block.
910 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600911 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600912 with cgroups.SimpleContainChildren('cros_sdk'):
913 with locking.FileLock(lock_path, 'chroot lock') as lock:
914 lock.write_lock()
915 if missing_image_tools:
916 logging.notice('Unmounting chroot.')
917 osutils.UmountTree(options.chroot)
918 else:
919 logging.notice('Deleting chroot.')
Don Garrett36650112018-06-28 15:54:34 -0700920 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600921 chroot_deleted = True
922
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600923 # If cleanup was requested, we have to do it while we're still in the original
924 # namespace. Since cleaning up the mount will interfere with any other
925 # commands, we exit here. The check above should have made sure that no other
926 # action was requested, anyway.
927 if options.unmount:
928 with locking.FileLock(lock_path, 'chroot lock') as lock:
929 lock.write_lock()
930 CleanupChroot(options.chroot)
931 sys.exit(0)
932
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600933 # Make sure the main chroot mount is visible. Contents will be filled in
934 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600935 if options.create and options.use_image:
936 if missing_image_tools:
937 raise SystemExit(
938 '''The tool(s) %s were not found.
939Please make sure the lvm2 and thin-provisioning-tools packages
940are installed on your host.
941Example(ubuntu):
942 sudo apt-get install lvm2 thin-provisioning-tools
943
944If you want to run without lvm2, pass --nouse-image (chroot
945snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600946
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600947 logging.debug('Making sure chroot image is mounted.')
948 with cgroups.SimpleContainChildren('cros_sdk'):
949 with locking.FileLock(lock_path, 'chroot lock') as lock:
950 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600951 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600952 cros_build_lib.Die('Unable to mount %s on chroot',
953 _ImageFileForChroot(options.chroot))
954 logging.notice('Mounted %s on chroot',
955 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600956
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600957 # Snapshot operations will always need the VG/LV, but other actions won't.
958 if any_snapshot_operation:
959 with cgroups.SimpleContainChildren('cros_sdk'):
960 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -0600961 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600962 options.chroot)
963 if not chroot_vg or not chroot_lv:
964 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
965 options.chroot)
966
967 # Delete snapshot before creating a new one. This allows the user to
968 # throw out old state, create a new snapshot, and enter the chroot in a
969 # single call to cros_sdk. Since restore involves deleting, also do it
970 # before creating.
971 if options.snapshot_restore:
972 lock.write_lock()
973 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
974 if options.snapshot_restore not in valid_snapshots:
975 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
976 'Valid snapshots: %s', options.snapshot_restore,
977 ', '.join(valid_snapshots))
978 osutils.UmountTree(options.chroot)
979 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
980 chroot_lv):
981 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600982 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600983 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
984
985 # Use a read lock for snapshot delete and create even though they modify
986 # the filesystem, because they don't modify the mounted chroot itself.
987 # The underlying LVM commands take their own locks, so conflicting
988 # concurrent operations here may crash cros_sdk, but won't corrupt the
989 # chroot image. This tradeoff seems worth it to allow snapshot
990 # operations on chroots that have a process inside.
991 if options.snapshot_delete:
992 lock.read_lock()
993 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
994
995 if options.snapshot_create:
996 lock.read_lock()
997 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
998 chroot_lv):
999 cros_build_lib.Die('Unable to create snapshot.')
1000
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001001 img_path = _ImageFileForChroot(options.chroot)
1002 if (options.use_image and os.path.exists(options.chroot) and
1003 os.path.exists(img_path)):
1004 img_stat = os.stat(img_path)
1005 img_used_bytes = img_stat.st_blocks * 512
1006
1007 mount_stat = os.statvfs(options.chroot)
1008 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1009 mount_stat.f_bfree)
1010
1011 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1012 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1013 logging.notice('%s is using %s GiB more than needed. Running '
1014 'fstrim.', img_path, extra_gbs)
1015 cmd = ['fstrim', options.chroot]
1016 try:
1017 cros_build_lib.RunCommand(cmd, print_cmd=False)
1018 except cros_build_lib.RunCommandError as e:
1019 logging.warning('Running fstrim failed. Consider running fstrim on '
1020 'your chroot manually.\nError: %s', e)
1021
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001022 # Enter a new set of namespaces. Everything after here cannot directly affect
1023 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001024 namespaces.SimpleUnshare()
1025 if options.ns_pid:
1026 first_pid = namespaces.CreatePidNs()
1027 else:
1028 first_pid = None
1029
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001030 if options.snapshot_list:
1031 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1032 print(snap)
1033 sys.exit(0)
1034
Brian Harringb938c782012-02-29 15:14:38 -08001035 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001036 sdk_version = (bootstrap_latest_version if options.bootstrap
1037 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001038 else:
1039 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001040 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001041 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001042
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001043 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001044 if options.download:
1045 if options.sdk_url:
1046 urls = [options.sdk_url]
1047 elif options.bootstrap:
1048 urls = GetStage3Urls(sdk_version)
Manoj Guptaa0cf99f2019-04-15 20:14:35 -07001049 if options.self_bootstrap:
1050 urls = GetArchStageTarballs(sdk_version)
Yong Hong4e29b622018-02-05 14:31:10 +08001051 else:
1052 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001053
Mike Frysinger80dfce92014-04-21 10:58:53 -04001054 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001055 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001056 if options.proxy_sim:
1057 _ProxySimSetup(options)
1058
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001059 if (options.delete and not chroot_deleted and
1060 (os.path.exists(options.chroot) or
1061 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001062 lock.write_lock()
1063 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001064
David James56e6c2c2012-10-24 23:54:41 -07001065 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1066 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001067 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001068
David James56e6c2c2012-10-24 23:54:41 -07001069 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001070 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001071 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001072 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001073 continue
1074 lock.write_lock(
1075 "Upgrade to %r needed but chroot is locked; please exit "
1076 "all instances so this upgrade can finish." % src)
1077 if not os.path.exists(src):
1078 # Note that while waiting for the write lock, src may've vanished;
1079 # it's a rare race during the upgrade process that's a byproduct
1080 # of us avoiding taking a write lock to do the src check. If we
1081 # took a write lock for that check, it would effectively limit
1082 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001083 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001084 elif not os.path.exists(target):
1085 # Upgrade occurred, but a reversion, or something whacky
1086 # occurred writing to the old location. Wipe and continue.
1087 os.rename(src, target)
1088 else:
1089 # Upgrade occurred once already, but either a reversion or
1090 # some before/after separate cros_sdk usage is at play.
1091 # Wipe and continue.
1092 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001093
David James56e6c2c2012-10-24 23:54:41 -07001094 if options.download:
1095 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001096 sdk_tarball = FetchRemoteTarballs(
1097 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001098
David James56e6c2c2012-10-24 23:54:41 -07001099 if options.create:
1100 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001101 # Recheck if the chroot is set up here before creating to make sure we
1102 # account for whatever the various delete/unmount/remount steps above
1103 # have done.
1104 if cros_sdk_lib.IsChrootReady(options.chroot):
1105 logging.debug('Chroot already exists. Skipping creation.')
1106 else:
1107 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
1108 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001109
David James56e6c2c2012-10-24 23:54:41 -07001110 if options.enter:
1111 lock.read_lock()
1112 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Mike Frysinger0b2d9ee2019-02-28 17:05:47 -05001113 options.chrome_root_mount, options.goma_dir,
1114 options.goma_client_json, options.working_dir,
1115 chroot_command)