blob: 6217b8c9e019722c705f0ff3a4504e5ddc6e6f96 [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,
Yong Hong84ba9172018-02-07 01:37:42 +0800225 workspace, goma_dir, goma_client_json, working_dir,
226 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800227 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400228 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
229 # The os.ST_NOSUID constant wasn't added until python-3.2.
230 if st.f_flag & 0x2:
231 cros_build_lib.Die('chroot cannot be in a nosuid mount')
232
Brian Harringae0a5322012-09-15 01:46:51 -0700233 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800234 if chrome_root:
235 cmd.extend(['--chrome_root', chrome_root])
236 if chrome_root_mount:
237 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700238 if workspace:
239 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900240 if goma_dir:
241 cmd.extend(['--goma_dir', goma_dir])
242 if goma_client_json:
243 cmd.extend(['--goma_client_json', goma_client_json])
Yong Hong84ba9172018-02-07 01:37:42 +0800244 if working_dir is not None:
245 cmd.extend(['--working_dir', working_dir])
Don Garrett230d1b22015-03-09 16:21:19 -0700246
Brian Harringb938c782012-02-29 15:14:38 -0800247 if len(additional_args) > 0:
248 cmd.append('--')
249 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700250
Ting-Yuan Huangf56d9af2017-06-19 16:08:32 -0700251 # ThinLTO opens lots of files at the same time.
252 resource.setrlimit(resource.RLIMIT_NOFILE, (32768, 32768))
Ralph Nathan549d3502015-03-26 17:38:42 -0700253 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
254 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700255 # If we were in interactive mode, ignore the exit code; it'll be whatever
256 # they last ran w/in the chroot and won't matter to us one way or another.
257 # Note this does allow chroot entrance to fail and be ignored during
258 # interactive; this is however a rare case and the user will immediately
259 # see it (nor will they be checking the exit code manually).
260 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700261 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800262
263
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600264def _ImageFileForChroot(chroot):
265 """Find the image file that should be associated with |chroot|.
266
267 This function does not check if the image exists; it simply returns the
268 filename that would be used.
269
270 Args:
271 chroot: Path to the chroot.
272
273 Returns:
274 Path to an image file that would be associated with chroot.
275 """
276 return chroot.rstrip('/') + '.img'
277
278
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600279def CreateChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
280 """Create a snapshot for the specified chroot VG/LV.
281
282 Args:
283 snapshot_name: The name of the new snapshot.
284 chroot_vg: The name of the VG containing the origin LV.
285 chroot_lv: The name of the origin LV.
286
287 Returns:
288 True if the snapshot was created, or False if a snapshot with the same
289 name already exists.
290
291 Raises:
292 SystemExit: The lvcreate command failed.
293 """
294 if snapshot_name in ListChrootSnapshots(chroot_vg, chroot_lv):
295 logging.error('Cannot create snapshot %s: A volume with that name already '
296 'exists.', snapshot_name)
297 return False
298
299 cmd = ['lvcreate', '-s', '--name', snapshot_name, '%s/%s' % (
300 chroot_vg, chroot_lv)]
301 try:
302 logging.notice('Creating snapshot %s from %s in VG %s.', snapshot_name,
303 chroot_lv, chroot_vg)
304 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
305 return True
306 except cros_build_lib.RunCommandError:
307 raise SystemExit('Running %r failed!' % cmd)
308
309
310def DeleteChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
311 """Delete the named snapshot from the specified chroot VG.
312
313 If the requested snapshot is not found, nothing happens. The main chroot LV
314 and internal thinpool LV cannot be deleted with this function.
315
316 Args:
317 snapshot_name: The name of the snapshot to delete.
318 chroot_vg: The name of the VG containing the origin LV.
319 chroot_lv: The name of the origin LV.
320
321 Raises:
322 SystemExit: The lvremove command failed.
323 """
Benjamin Gordon74645232018-05-04 17:40:42 -0600324 if snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
325 cros_sdk_lib.CHROOT_THINPOOL_NAME):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600326 logging.error('Cannot remove LV %s as a snapshot. Use cros_sdk --delete '
327 'if you want to remove the whole chroot.', snapshot_name)
328 return
329
330 if snapshot_name not in ListChrootSnapshots(chroot_vg, chroot_lv):
331 return
332
333 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, snapshot_name)]
334 try:
335 logging.notice('Deleting snapshot %s in VG %s.', snapshot_name, chroot_vg)
336 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
337 except cros_build_lib.RunCommandError:
338 raise SystemExit('Running %r failed!' % cmd)
339
340
341def RestoreChrootSnapshot(snapshot_name, chroot_vg, chroot_lv):
342 """Restore the chroot to an existing snapshot.
343
344 This is done by renaming the original |chroot_lv| LV to a temporary name,
345 renaming the snapshot named |snapshot_name| to |chroot_lv|, and deleting the
346 now unused LV. If an error occurs, attempts to rename the original snapshot
347 back to |chroot_lv| to leave the chroot unchanged.
348
349 The chroot must be unmounted before calling this function, and will be left
350 unmounted after this function returns.
351
352 Args:
353 snapshot_name: The name of the snapshot to restore. This snapshot will no
354 longer be accessible at its original name after this function finishes.
355 chroot_vg: The VG containing the chroot LV and snapshot LV.
356 chroot_lv: The name of the original chroot LV.
357
358 Returns:
359 True if the chroot was restored to the requested snapshot, or False if
360 the snapshot wasn't found or isn't valid.
361
362 Raises:
363 SystemExit: Any of the LVM commands failed.
364 """
365 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
Benjamin Gordon74645232018-05-04 17:40:42 -0600366 if (snapshot_name in (cros_sdk_lib.CHROOT_LV_NAME,
367 cros_sdk_lib.CHROOT_THINPOOL_NAME) or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600368 snapshot_name not in valid_snapshots):
369 logging.error('Chroot cannot be restored to %s. Valid snapshots: %s',
370 snapshot_name, ', '.join(valid_snapshots))
371 return False
372
373 backup_chroot_name = 'chroot-bak-%d' % random.randint(0, 1000)
374 cmd = ['lvrename', chroot_vg, chroot_lv, backup_chroot_name]
375 try:
376 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
377 except cros_build_lib.RunCommandError:
378 raise SystemExit('Running %r failed!' % cmd)
379
380 cmd = ['lvrename', chroot_vg, snapshot_name, chroot_lv]
381 try:
382 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
383 except cros_build_lib.RunCommandError:
384 cmd = ['lvrename', chroot_vg, backup_chroot_name, chroot_lv]
385 try:
386 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
387 except cros_build_lib.RunCommandError:
388 raise SystemExit('Failed to rename %s to chroot and failed to restore '
389 '%s back to chroot. Failed command: %r' %
390 (snapshot_name, backup_chroot_name, cmd))
391 raise SystemExit('Failed to rename %s to chroot. Original chroot LV has '
392 'been restored. Failed command: %r' %
393 (snapshot_name, cmd))
394
395 # Some versions of LVM set snapshots to be skipped at auto-activate time.
396 # Other versions don't have this flag at all. We run lvchange to try
397 # disabling auto-skip and activating the volume, but ignore errors. Versions
398 # that don't have the flag should be auto-activated.
399 chroot_lv_path = '%s/%s' % (chroot_vg, chroot_lv)
400 cmd = ['lvchange', '-kn', chroot_lv_path]
401 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True,
402 error_code_ok=True)
403
404 # Activate the LV in case the lvchange above was needed. Activating an LV
405 # that is already active shouldn't do anything, so this is safe to run even if
406 # the -kn wasn't needed.
407 cmd = ['lvchange', '-ay', chroot_lv_path]
408 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
409
410 cmd = ['lvremove', '-f', '%s/%s' % (chroot_vg, backup_chroot_name)]
411 try:
412 cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
413 except cros_build_lib.RunCommandError:
414 raise SystemExit('Failed to remove backup LV %s/%s. Failed command: %r' %
415 (chroot_vg, backup_chroot_name, cmd))
416
417 return True
418
419
420def ListChrootSnapshots(chroot_vg, chroot_lv):
421 """Return all snapshots in |chroot_vg| regardless of origin volume.
422
423 Args:
424 chroot_vg: The name of the VG containing the chroot.
425 chroot_lv: The name of the chroot LV.
426
427 Returns:
428 A (possibly-empty) list of snapshot LVs found in |chroot_vg|.
429
430 Raises:
431 SystemExit: The lvs command failed.
432 """
433 if not chroot_vg or not chroot_lv:
434 return []
435
436 cmd = ['lvs', '-o', 'lv_name,pool_lv,lv_attr', '-O', 'lv_name',
437 '--noheadings', '--separator', '\t', chroot_vg]
438 try:
439 result = cros_build_lib.RunCommand(cmd, print_cmd=False,
440 redirect_stdout=True)
441 except cros_build_lib.RunCommandError:
442 raise SystemExit('Running %r failed!' % cmd)
443
444 # Once the thin origin volume has been deleted, there's no way to tell a
445 # snapshot apart from any other volume. Since this VG is created and managed
446 # by cros_sdk, we'll assume that all volumes that share the same thin pool are
447 # valid snapshots.
448 snapshots = []
449 snapshot_attrs = re.compile(r'^V.....t.{2,}') # Matches a thin volume.
450 for line in result.output.splitlines():
451 lv_name, pool_lv, lv_attr = line.lstrip().split('\t')
452 if (lv_name == chroot_lv or
Benjamin Gordon74645232018-05-04 17:40:42 -0600453 lv_name == cros_sdk_lib.CHROOT_THINPOOL_NAME or
454 pool_lv != cros_sdk_lib.CHROOT_THINPOOL_NAME or
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600455 not snapshot_attrs.match(lv_attr)):
456 continue
457 snapshots.append(lv_name)
458 return snapshots
459
460
David James56e6c2c2012-10-24 23:54:41 -0700461def _SudoCommand():
462 """Get the 'sudo' command, along with all needed environment variables."""
463
David James5a73b4d2013-03-07 10:23:40 -0800464 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
465 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700466 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800467 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700468 value = os.environ.get(key)
469 if value is not None:
470 cmd += ['%s=%s' % (key, value)]
471
472 # Pass in the path to the depot_tools so that users can access them from
473 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400474 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500475
David James56e6c2c2012-10-24 23:54:41 -0700476 return cmd
477
478
Josh Triplett472a4182013-03-08 11:48:57 -0800479def _ReportMissing(missing):
480 """Report missing utilities, then exit.
481
482 Args:
483 missing: List of missing utilities, as returned by
484 osutils.FindMissingBinaries. If non-empty, will not return.
485 """
486
487 if missing:
488 raise SystemExit(
489 'The tool(s) %s were not found.\n'
490 'Please install the appropriate package in your host.\n'
491 'Example(ubuntu):\n'
492 ' sudo apt-get install <packagename>'
493 % ', '.join(missing))
494
495
496def _ProxySimSetup(options):
497 """Set up proxy simulator, and return only in the child environment.
498
499 TODO: Ideally, this should support multiple concurrent invocations of
500 cros_sdk --proxy-sim; currently, such invocations will conflict with each
501 other due to the veth device names and IP addresses. Either this code would
502 need to generate fresh, unused names for all of these before forking, or it
503 would need to support multiple concurrent cros_sdk invocations sharing one
504 proxy and allowing it to exit when unused (without counting on any local
505 service-management infrastructure on the host).
506 """
507
508 may_need_mpm = False
509 apache_bin = osutils.Which('apache2')
510 if apache_bin is None:
511 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
512 if apache_bin is None:
513 _ReportMissing(('apache2',))
514 else:
515 may_need_mpm = True
516
517 # Module names and .so names included for ease of grepping.
518 apache_modules = [('proxy_module', 'mod_proxy.so'),
519 ('proxy_connect_module', 'mod_proxy_connect.so'),
520 ('proxy_http_module', 'mod_proxy_http.so'),
521 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
522
523 # Find the apache module directory, and make sure it has the modules we need.
524 module_dirs = {}
525 for g in PROXY_APACHE_MODULE_GLOBS:
526 for mod, so in apache_modules:
527 for f in glob.glob(os.path.join(g, so)):
528 module_dirs.setdefault(os.path.dirname(f), []).append(so)
529 for apache_module_path, modules_found in module_dirs.iteritems():
530 if len(modules_found) == len(apache_modules):
531 break
532 else:
533 # Appease cros lint, which doesn't understand that this else block will not
534 # fall through to the subsequent code which relies on apache_module_path.
535 apache_module_path = None
536 raise SystemExit(
537 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500538 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800539
540 def check_add_module(name):
541 so = 'mod_%s.so' % name
542 if os.access(os.path.join(apache_module_path, so), os.F_OK):
543 mod = '%s_module' % name
544 apache_modules.append((mod, so))
545 return True
546 return False
547
548 check_add_module('authz_core')
549 if may_need_mpm:
550 for mpm in PROXY_APACHE_MPMS:
551 if check_add_module('mpm_%s' % mpm):
552 break
553
554 veth_host = '%s-host' % PROXY_VETH_PREFIX
555 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
556
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500557 # Set up locks to sync the net namespace setup. We need the child to create
558 # the net ns first, and then have the parent assign the guest end of the veth
559 # interface to the child's new network namespace & bring up the proxy. Only
560 # then can the child move forward and rely on the network being up.
561 ns_create_lock = locking.PipeLock()
562 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800563
564 pid = os.fork()
565 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500566 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800567 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500568
569 # Signal the parent the ns is ready to be configured.
570 ns_create_lock.Post()
571 del ns_create_lock
572
573 # Wait for the parent to finish setting up the ns/proxy.
574 ns_setup_lock.Wait()
575 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800576
577 # Set up child side of the network.
578 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500579 ('ip', 'link', 'set', 'up', 'lo'),
580 ('ip', 'address', 'add',
581 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
582 'dev', veth_guest),
583 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800584 )
585 try:
586 for cmd in commands:
587 cros_build_lib.RunCommand(cmd, print_cmd=False)
588 except cros_build_lib.RunCommandError:
589 raise SystemExit('Running %r failed!' % (cmd,))
590
591 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
592 for proto in ('http', 'https', 'ftp'):
593 os.environ[proto + '_proxy'] = proxy_url
594 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
595 os.environ.pop(v, None)
596 return
597
Josh Triplett472a4182013-03-08 11:48:57 -0800598 # Set up parent side of the network.
599 uid = int(os.environ.get('SUDO_UID', '0'))
600 gid = int(os.environ.get('SUDO_GID', '0'))
601 if uid == 0 or gid == 0:
602 for username in PROXY_APACHE_FALLBACK_USERS:
603 try:
604 pwnam = pwd.getpwnam(username)
605 uid, gid = pwnam.pw_uid, pwnam.pw_gid
606 break
607 except KeyError:
608 continue
609 if uid == 0 or gid == 0:
610 raise SystemExit('Could not find a non-root user to run Apache as')
611
612 chroot_parent, chroot_base = os.path.split(options.chroot)
613 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
614 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
615
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500616 # Wait for the child to create the net ns.
617 ns_create_lock.Wait()
618 del ns_create_lock
619
Josh Triplett472a4182013-03-08 11:48:57 -0800620 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500621 'User #%u' % uid,
622 'Group #%u' % gid,
623 'PidFile %s' % pid_file,
624 'ErrorLog %s' % log_file,
625 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
626 'ServerName %s' % PROXY_HOST_IP,
627 'ProxyRequests On',
628 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800629 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500630 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
631 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800632 ]
633 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500634 ('ip', 'link', 'add', 'name', veth_host,
635 'type', 'veth', 'peer', 'name', veth_guest),
636 ('ip', 'address', 'add',
637 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
638 'dev', veth_host),
639 ('ip', 'link', 'set', veth_host, 'up'),
640 ([apache_bin, '-f', '/dev/null'] +
641 [arg for d in apache_directives for arg in ('-C', d)]),
642 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800643 )
644 cmd = None # Make cros lint happy.
645 try:
646 for cmd in commands:
647 cros_build_lib.RunCommand(cmd, print_cmd=False)
648 except cros_build_lib.RunCommandError:
649 # Clean up existing interfaces, if any.
650 cmd_cleanup = ('ip', 'link', 'del', veth_host)
651 try:
652 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
653 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700654 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800655 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500656
657 # Signal the child that the net ns/proxy is fully configured now.
658 ns_setup_lock.Post()
659 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800660
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400661 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800662
663
Mike Frysingera78a56e2012-11-20 06:02:30 -0500664def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700665 """Re-execute cros_sdk as root.
666
667 Also unshare the mount namespace so as to ensure that processes outside
668 the chroot can't mess with our mounts.
669 """
670 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500671 cmd = _SudoCommand() + ['--'] + argv
672 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500673 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400674 # We must set up the cgroups mounts before we enter our own namespace.
675 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800676 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700677
678
Mike Frysinger34db8692013-11-11 14:54:08 -0500679def _CreateParser(sdk_latest_version, bootstrap_latest_version):
680 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400681 usage = ('usage: %(prog)s [options] '
682 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
683 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
684 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700685
Mike Frysinger34db8692013-11-11 14:54:08 -0500686 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500687 default_chroot = os.path.join(constants.SOURCE_ROOT,
688 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400689 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700690 '--chroot', dest='chroot', default=default_chroot, type='path',
691 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600692 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
693 default=True,
694 help='Do not mount the chroot on a loopback image; '
695 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800696
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400697 parser.add_argument('--chrome_root', type='path',
698 help='Mount this chrome root into the SDK chroot')
699 parser.add_argument('--chrome_root_mount', type='path',
700 help='Mount chrome into this path inside SDK chroot')
701 parser.add_argument('--nousepkg', action='store_true', default=False,
702 help='Do not use binary packages when creating a chroot.')
703 parser.add_argument('-u', '--url', dest='sdk_url',
704 help='Use sdk tarball located at this url. Use file:// '
705 'for local files.')
706 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)))
710 parser.add_argument('--workspace',
711 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900712 parser.add_argument('--goma_dir', type='path',
713 help='Goma installed directory to mount into the chroot.')
714 parser.add_argument('--goma_client_json', type='path',
715 help='Service account json file to use goma on bot. '
716 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800717
718 # Use type=str instead of type='path' to prevent the given path from being
719 # transfered to absolute path automatically.
720 parser.add_argument('--working-dir', type=str,
721 help='Run the command in specific working directory in '
722 'chroot. If the given directory is a relative '
723 'path, this program will transfer the path to '
724 'the corresponding one inside chroot.')
725
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400726 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500727
728 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400729 group = parser.add_argument_group('Commands')
730 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500731 '--enter', action='store_true', default=False,
732 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400733 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500734 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500735 help='Create the chroot only if it does not already exist. '
736 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400737 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500738 '--bootstrap', action='store_true', default=False,
739 help='Build everything from scratch, including the sdk. '
740 'Use this only if you need to validate a change '
741 'that affects SDK creation itself (toolchain and '
742 'build are typically the only folk who need this). '
743 'Note this will quite heavily slow down the build. '
744 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400745 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500746 '-r', '--replace', action='store_true', default=False,
747 help='Replace an existing SDK chroot. Basically an alias '
748 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400749 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500750 '--delete', action='store_true', default=False,
751 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400752 group.add_argument(
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600753 '--unmount', action='store_true', default=False,
754 help='Unmount and clean up devices associated with the '
755 'SDK chroot if it exists. This does not delete the '
756 'backing image file, so the same chroot can be later '
757 're-mounted for reuse. To fully delete the chroot, use '
758 '--delete. This is primarily useful for working on '
759 'cros_sdk or the chroot setup; you should not need it '
760 'under normal circumstances.')
761 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500762 '--download', action='store_true', default=False,
763 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600764 group.add_argument(
765 '--snapshot-create', metavar='SNAPSHOT_NAME',
766 help='Create a snapshot of the chroot. Requires that the chroot was '
767 'created without the --nouse-image option.')
768 group.add_argument(
769 '--snapshot-restore', metavar='SNAPSHOT_NAME',
770 help='Restore the chroot to a previously created snapshot.')
771 group.add_argument(
772 '--snapshot-delete', metavar='SNAPSHOT_NAME',
773 help='Delete a previously created snapshot. Deleting a snapshot that '
774 'does not exist is not an error.')
775 group.add_argument(
776 '--snapshot-list', action='store_true', default=False,
777 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500778 commands = group
779
Mike Frysinger80dfce92014-04-21 10:58:53 -0400780 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400781 group = parser.add_argument_group('Namespaces')
782 group.add_argument('--proxy-sim', action='store_true', default=False,
783 help='Simulate a restrictive network requiring an outbound'
784 ' proxy.')
785 group.add_argument('--no-ns-pid', dest='ns_pid',
786 default=True, action='store_false',
787 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400788
Mike Frysinger34db8692013-11-11 14:54:08 -0500789 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400790 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500791 'Internal Chromium OS Build Team Options',
792 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400793 group.add_argument('--buildbot-log-version', default=False,
794 action='store_true',
795 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500796
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400797 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500798
799
800def main(argv):
801 conf = cros_build_lib.LoadKeyValueFile(
802 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
803 ignore_missing=True)
804 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
805 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
806 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400807 options = parser.parse_args(argv)
808 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800809
810 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500811 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800812
Brian Harring1790ac42012-09-23 08:53:33 -0700813 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700814 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600815 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700816 "cros_sdk is currently only supported on x86_64; you're running"
817 " %s. Please find a x86_64 machine." % (host,))
818
Josh Triplett472a4182013-03-08 11:48:57 -0800819 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
820 if options.proxy_sim:
821 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600822 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800823
Benjamin Gordon040a1162017-06-29 13:44:47 -0600824 if (sdk_latest_version == '<unknown>' or
825 bootstrap_latest_version == '<unknown>'):
826 cros_build_lib.Die(
827 'No SDK version was found. '
828 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
829 'Please change to a directory inside your Chromium OS source tree\n'
830 'and retry. If you need to setup a Chromium OS source tree, see\n'
Mike Frysingerdcad4e02018-08-03 16:20:02 -0400831 ' https://dev.chromium.org/chromium-os/developer-guide')
Benjamin Gordon040a1162017-06-29 13:44:47 -0600832
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600833 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
834 or options.snapshot_delete or options.snapshot_list)
835 if any_snapshot_operation and not options.use_image:
836 cros_build_lib.Die('Snapshot operations are not compatible with '
837 '--nouse-image.')
838
839 if (options.snapshot_delete and options.snapshot_delete ==
840 options.snapshot_restore):
841 parser.error('Cannot --snapshot_delete the same snapshot you are '
842 'restoring with --snapshot_restore.')
843
David James471532c2013-01-21 10:23:31 -0800844 _ReExecuteIfNeeded([sys.argv[0]] + argv)
845
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600846 lock_path = os.path.dirname(options.chroot)
847 lock_path = os.path.join(
848 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
849
Brian Harring218e13c2012-10-10 16:21:26 -0700850 # Expand out the aliases...
851 if options.replace:
852 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800853
Brian Harring218e13c2012-10-10 16:21:26 -0700854 if options.bootstrap:
855 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800856
Brian Harring218e13c2012-10-10 16:21:26 -0700857 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400858 # pylint: disable=protected-access
859 # This _group_actions access sucks, but upstream decided to not include an
860 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700861 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400862 for x in commands._group_actions)
863 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700864 options.enter |= bool(chroot_command)
865
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600866 if (options.delete and not options.create and
867 (options.enter or any_snapshot_operation)):
868 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700869 "was specified makes no sense.")
870
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600871 if (options.unmount and
872 (options.create or options.enter or any_snapshot_operation)):
873 parser.error('--unmount cannot be specified with other chroot actions.')
874
Yong Hong84ba9172018-02-07 01:37:42 +0800875 if options.working_dir is not None and not os.path.isabs(options.working_dir):
876 options.working_dir = path_util.ToChrootPath(options.working_dir)
877
Benjamin Gordon35194f12017-07-19 10:26:22 -0600878 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600879 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600880 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600881 not options.unmount and not missing_image_tools and
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600882 os.path.exists(_ImageFileForChroot(options.chroot))):
883 # Try to re-mount an existing image in case the user has rebooted.
884 with cgroups.SimpleContainChildren('cros_sdk'):
885 with locking.FileLock(lock_path, 'chroot lock') as lock:
886 logging.debug('Checking if existing chroot image can be mounted.')
887 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600888 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600889 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600890 if chroot_exists:
891 logging.notice('Mounted existing image %s on chroot',
892 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700893
894 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600895 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700896 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800897
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600898 # Make sure we will download if we plan to create.
899 options.download |= options.create
900
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600901 # Anything that needs to manipulate the main chroot mount or communicate with
902 # LVM needs to be done here before we enter the new namespaces.
903
904 # If deleting, do it regardless of the use_image flag so that a
905 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600906 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
907 # favor of this block.
908 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600909 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600910 with cgroups.SimpleContainChildren('cros_sdk'):
911 with locking.FileLock(lock_path, 'chroot lock') as lock:
912 lock.write_lock()
913 if missing_image_tools:
914 logging.notice('Unmounting chroot.')
915 osutils.UmountTree(options.chroot)
916 else:
917 logging.notice('Deleting chroot.')
Don Garrett36650112018-06-28 15:54:34 -0700918 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600919 chroot_deleted = True
920
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600921 # If cleanup was requested, we have to do it while we're still in the original
922 # namespace. Since cleaning up the mount will interfere with any other
923 # commands, we exit here. The check above should have made sure that no other
924 # action was requested, anyway.
925 if options.unmount:
926 with locking.FileLock(lock_path, 'chroot lock') as lock:
927 lock.write_lock()
928 CleanupChroot(options.chroot)
929 sys.exit(0)
930
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600931 # Make sure the main chroot mount is visible. Contents will be filled in
932 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600933 if options.create and options.use_image:
934 if missing_image_tools:
935 raise SystemExit(
936 '''The tool(s) %s were not found.
937Please make sure the lvm2 and thin-provisioning-tools packages
938are installed on your host.
939Example(ubuntu):
940 sudo apt-get install lvm2 thin-provisioning-tools
941
942If you want to run without lvm2, pass --nouse-image (chroot
943snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600944
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600945 logging.debug('Making sure chroot image is mounted.')
946 with cgroups.SimpleContainChildren('cros_sdk'):
947 with locking.FileLock(lock_path, 'chroot lock') as lock:
948 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600949 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600950 cros_build_lib.Die('Unable to mount %s on chroot',
951 _ImageFileForChroot(options.chroot))
952 logging.notice('Mounted %s on chroot',
953 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600954
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600955 # Snapshot operations will always need the VG/LV, but other actions won't.
956 if any_snapshot_operation:
957 with cgroups.SimpleContainChildren('cros_sdk'):
958 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -0600959 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600960 options.chroot)
961 if not chroot_vg or not chroot_lv:
962 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
963 options.chroot)
964
965 # Delete snapshot before creating a new one. This allows the user to
966 # throw out old state, create a new snapshot, and enter the chroot in a
967 # single call to cros_sdk. Since restore involves deleting, also do it
968 # before creating.
969 if options.snapshot_restore:
970 lock.write_lock()
971 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
972 if options.snapshot_restore not in valid_snapshots:
973 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
974 'Valid snapshots: %s', options.snapshot_restore,
975 ', '.join(valid_snapshots))
976 osutils.UmountTree(options.chroot)
977 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
978 chroot_lv):
979 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600980 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600981 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
982
983 # Use a read lock for snapshot delete and create even though they modify
984 # the filesystem, because they don't modify the mounted chroot itself.
985 # The underlying LVM commands take their own locks, so conflicting
986 # concurrent operations here may crash cros_sdk, but won't corrupt the
987 # chroot image. This tradeoff seems worth it to allow snapshot
988 # operations on chroots that have a process inside.
989 if options.snapshot_delete:
990 lock.read_lock()
991 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
992
993 if options.snapshot_create:
994 lock.read_lock()
995 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
996 chroot_lv):
997 cros_build_lib.Die('Unable to create snapshot.')
998
Benjamin Gordone3d5bd12017-11-16 15:42:28 -0700999 img_path = _ImageFileForChroot(options.chroot)
1000 if (options.use_image and os.path.exists(options.chroot) and
1001 os.path.exists(img_path)):
1002 img_stat = os.stat(img_path)
1003 img_used_bytes = img_stat.st_blocks * 512
1004
1005 mount_stat = os.statvfs(options.chroot)
1006 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1007 mount_stat.f_bfree)
1008
1009 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1010 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1011 logging.notice('%s is using %s GiB more than needed. Running '
1012 'fstrim.', img_path, extra_gbs)
1013 cmd = ['fstrim', options.chroot]
1014 try:
1015 cros_build_lib.RunCommand(cmd, print_cmd=False)
1016 except cros_build_lib.RunCommandError as e:
1017 logging.warning('Running fstrim failed. Consider running fstrim on '
1018 'your chroot manually.\nError: %s', e)
1019
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001020 # Enter a new set of namespaces. Everything after here cannot directly affect
1021 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001022 namespaces.SimpleUnshare()
1023 if options.ns_pid:
1024 first_pid = namespaces.CreatePidNs()
1025 else:
1026 first_pid = None
1027
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001028 if options.snapshot_list:
1029 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1030 print(snap)
1031 sys.exit(0)
1032
Brian Harringb938c782012-02-29 15:14:38 -08001033 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001034 sdk_version = (bootstrap_latest_version if options.bootstrap
1035 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001036 else:
1037 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001038 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001039 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001040
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001041 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001042 if options.download:
1043 if options.sdk_url:
1044 urls = [options.sdk_url]
1045 elif options.bootstrap:
1046 urls = GetStage3Urls(sdk_version)
1047 else:
1048 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001049
Mike Frysinger80dfce92014-04-21 10:58:53 -04001050 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001051 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001052 if options.proxy_sim:
1053 _ProxySimSetup(options)
1054
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001055 if (options.delete and not chroot_deleted and
1056 (os.path.exists(options.chroot) or
1057 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001058 lock.write_lock()
1059 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001060
David James56e6c2c2012-10-24 23:54:41 -07001061 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1062 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001063 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001064
David James56e6c2c2012-10-24 23:54:41 -07001065 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001066 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001067 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001068 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001069 continue
1070 lock.write_lock(
1071 "Upgrade to %r needed but chroot is locked; please exit "
1072 "all instances so this upgrade can finish." % src)
1073 if not os.path.exists(src):
1074 # Note that while waiting for the write lock, src may've vanished;
1075 # it's a rare race during the upgrade process that's a byproduct
1076 # of us avoiding taking a write lock to do the src check. If we
1077 # took a write lock for that check, it would effectively limit
1078 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001079 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001080 elif not os.path.exists(target):
1081 # Upgrade occurred, but a reversion, or something whacky
1082 # occurred writing to the old location. Wipe and continue.
1083 os.rename(src, target)
1084 else:
1085 # Upgrade occurred once already, but either a reversion or
1086 # some before/after separate cros_sdk usage is at play.
1087 # Wipe and continue.
1088 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001089
David James56e6c2c2012-10-24 23:54:41 -07001090 if options.download:
1091 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001092 sdk_tarball = FetchRemoteTarballs(
1093 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001094
David James56e6c2c2012-10-24 23:54:41 -07001095 if options.create:
1096 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001097 # Recheck if the chroot is set up here before creating to make sure we
1098 # account for whatever the various delete/unmount/remount steps above
1099 # have done.
1100 if cros_sdk_lib.IsChrootReady(options.chroot):
1101 logging.debug('Chroot already exists. Skipping creation.')
1102 else:
1103 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
1104 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001105
David James56e6c2c2012-10-24 23:54:41 -07001106 if options.enter:
1107 lock.read_lock()
1108 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001109 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001110 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001111 options.working_dir, chroot_command)