blob: eec06a04aa653c9c2084574eb193b51db632d66c [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
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600461def _FindSubmounts(*args):
462 """Find all mounts matching each of the paths in |args| and any submounts.
463
464 Returns:
465 A list of all matching mounts in the order found in /proc/mounts.
466 """
467 mounts = []
468 paths = [p.rstrip('/') for p in args]
469 for mtab in osutils.IterateMountPoints():
470 for path in paths:
471 if mtab.destination == path or mtab.destination.startswith(path + '/'):
472 mounts.append(mtab.destination)
473 break
474
475 return mounts
476
477
David James56e6c2c2012-10-24 23:54:41 -0700478def _SudoCommand():
479 """Get the 'sudo' command, along with all needed environment variables."""
480
David James5a73b4d2013-03-07 10:23:40 -0800481 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
482 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700483 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800484 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700485 value = os.environ.get(key)
486 if value is not None:
487 cmd += ['%s=%s' % (key, value)]
488
489 # Pass in the path to the depot_tools so that users can access them from
490 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400491 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500492
David James56e6c2c2012-10-24 23:54:41 -0700493 return cmd
494
495
Josh Triplett472a4182013-03-08 11:48:57 -0800496def _ReportMissing(missing):
497 """Report missing utilities, then exit.
498
499 Args:
500 missing: List of missing utilities, as returned by
501 osutils.FindMissingBinaries. If non-empty, will not return.
502 """
503
504 if missing:
505 raise SystemExit(
506 'The tool(s) %s were not found.\n'
507 'Please install the appropriate package in your host.\n'
508 'Example(ubuntu):\n'
509 ' sudo apt-get install <packagename>'
510 % ', '.join(missing))
511
512
513def _ProxySimSetup(options):
514 """Set up proxy simulator, and return only in the child environment.
515
516 TODO: Ideally, this should support multiple concurrent invocations of
517 cros_sdk --proxy-sim; currently, such invocations will conflict with each
518 other due to the veth device names and IP addresses. Either this code would
519 need to generate fresh, unused names for all of these before forking, or it
520 would need to support multiple concurrent cros_sdk invocations sharing one
521 proxy and allowing it to exit when unused (without counting on any local
522 service-management infrastructure on the host).
523 """
524
525 may_need_mpm = False
526 apache_bin = osutils.Which('apache2')
527 if apache_bin is None:
528 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
529 if apache_bin is None:
530 _ReportMissing(('apache2',))
531 else:
532 may_need_mpm = True
533
534 # Module names and .so names included for ease of grepping.
535 apache_modules = [('proxy_module', 'mod_proxy.so'),
536 ('proxy_connect_module', 'mod_proxy_connect.so'),
537 ('proxy_http_module', 'mod_proxy_http.so'),
538 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
539
540 # Find the apache module directory, and make sure it has the modules we need.
541 module_dirs = {}
542 for g in PROXY_APACHE_MODULE_GLOBS:
543 for mod, so in apache_modules:
544 for f in glob.glob(os.path.join(g, so)):
545 module_dirs.setdefault(os.path.dirname(f), []).append(so)
546 for apache_module_path, modules_found in module_dirs.iteritems():
547 if len(modules_found) == len(apache_modules):
548 break
549 else:
550 # Appease cros lint, which doesn't understand that this else block will not
551 # fall through to the subsequent code which relies on apache_module_path.
552 apache_module_path = None
553 raise SystemExit(
554 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500555 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800556
557 def check_add_module(name):
558 so = 'mod_%s.so' % name
559 if os.access(os.path.join(apache_module_path, so), os.F_OK):
560 mod = '%s_module' % name
561 apache_modules.append((mod, so))
562 return True
563 return False
564
565 check_add_module('authz_core')
566 if may_need_mpm:
567 for mpm in PROXY_APACHE_MPMS:
568 if check_add_module('mpm_%s' % mpm):
569 break
570
571 veth_host = '%s-host' % PROXY_VETH_PREFIX
572 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
573
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500574 # Set up locks to sync the net namespace setup. We need the child to create
575 # the net ns first, and then have the parent assign the guest end of the veth
576 # interface to the child's new network namespace & bring up the proxy. Only
577 # then can the child move forward and rely on the network being up.
578 ns_create_lock = locking.PipeLock()
579 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800580
581 pid = os.fork()
582 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500583 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800584 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500585
586 # Signal the parent the ns is ready to be configured.
587 ns_create_lock.Post()
588 del ns_create_lock
589
590 # Wait for the parent to finish setting up the ns/proxy.
591 ns_setup_lock.Wait()
592 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800593
594 # Set up child side of the network.
595 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500596 ('ip', 'link', 'set', 'up', 'lo'),
597 ('ip', 'address', 'add',
598 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
599 'dev', veth_guest),
600 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800601 )
602 try:
603 for cmd in commands:
604 cros_build_lib.RunCommand(cmd, print_cmd=False)
605 except cros_build_lib.RunCommandError:
606 raise SystemExit('Running %r failed!' % (cmd,))
607
608 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
609 for proto in ('http', 'https', 'ftp'):
610 os.environ[proto + '_proxy'] = proxy_url
611 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
612 os.environ.pop(v, None)
613 return
614
Josh Triplett472a4182013-03-08 11:48:57 -0800615 # Set up parent side of the network.
616 uid = int(os.environ.get('SUDO_UID', '0'))
617 gid = int(os.environ.get('SUDO_GID', '0'))
618 if uid == 0 or gid == 0:
619 for username in PROXY_APACHE_FALLBACK_USERS:
620 try:
621 pwnam = pwd.getpwnam(username)
622 uid, gid = pwnam.pw_uid, pwnam.pw_gid
623 break
624 except KeyError:
625 continue
626 if uid == 0 or gid == 0:
627 raise SystemExit('Could not find a non-root user to run Apache as')
628
629 chroot_parent, chroot_base = os.path.split(options.chroot)
630 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
631 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
632
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500633 # Wait for the child to create the net ns.
634 ns_create_lock.Wait()
635 del ns_create_lock
636
Josh Triplett472a4182013-03-08 11:48:57 -0800637 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500638 'User #%u' % uid,
639 'Group #%u' % gid,
640 'PidFile %s' % pid_file,
641 'ErrorLog %s' % log_file,
642 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
643 'ServerName %s' % PROXY_HOST_IP,
644 'ProxyRequests On',
645 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800646 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500647 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
648 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800649 ]
650 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500651 ('ip', 'link', 'add', 'name', veth_host,
652 'type', 'veth', 'peer', 'name', veth_guest),
653 ('ip', 'address', 'add',
654 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
655 'dev', veth_host),
656 ('ip', 'link', 'set', veth_host, 'up'),
657 ([apache_bin, '-f', '/dev/null'] +
658 [arg for d in apache_directives for arg in ('-C', d)]),
659 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800660 )
661 cmd = None # Make cros lint happy.
662 try:
663 for cmd in commands:
664 cros_build_lib.RunCommand(cmd, print_cmd=False)
665 except cros_build_lib.RunCommandError:
666 # Clean up existing interfaces, if any.
667 cmd_cleanup = ('ip', 'link', 'del', veth_host)
668 try:
669 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
670 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700671 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800672 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500673
674 # Signal the child that the net ns/proxy is fully configured now.
675 ns_setup_lock.Post()
676 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800677
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400678 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800679
680
Mike Frysingera78a56e2012-11-20 06:02:30 -0500681def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700682 """Re-execute cros_sdk as root.
683
684 Also unshare the mount namespace so as to ensure that processes outside
685 the chroot can't mess with our mounts.
686 """
687 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500688 cmd = _SudoCommand() + ['--'] + argv
689 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500690 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400691 # We must set up the cgroups mounts before we enter our own namespace.
692 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800693 cgroups.Cgroup.InitSystem()
David James56e6c2c2012-10-24 23:54:41 -0700694
695
Mike Frysinger34db8692013-11-11 14:54:08 -0500696def _CreateParser(sdk_latest_version, bootstrap_latest_version):
697 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400698 usage = ('usage: %(prog)s [options] '
699 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
700 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
701 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700702
Mike Frysinger34db8692013-11-11 14:54:08 -0500703 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500704 default_chroot = os.path.join(constants.SOURCE_ROOT,
705 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400706 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700707 '--chroot', dest='chroot', default=default_chroot, type='path',
708 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600709 parser.add_argument('--nouse-image', dest='use_image', action='store_false',
710 default=True,
711 help='Do not mount the chroot on a loopback image; '
712 'instead, create it directly in a directory.')
Brian Harringb938c782012-02-29 15:14:38 -0800713
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400714 parser.add_argument('--chrome_root', type='path',
715 help='Mount this chrome root into the SDK chroot')
716 parser.add_argument('--chrome_root_mount', type='path',
717 help='Mount chrome into this path inside SDK chroot')
718 parser.add_argument('--nousepkg', action='store_true', default=False,
719 help='Do not use binary packages when creating a chroot.')
720 parser.add_argument('-u', '--url', dest='sdk_url',
721 help='Use sdk tarball located at this url. Use file:// '
722 'for local files.')
723 parser.add_argument('--sdk-version',
724 help=('Use this sdk version. For prebuilt, current is %r'
725 ', for bootstrapping it is %r.'
726 % (sdk_latest_version, bootstrap_latest_version)))
727 parser.add_argument('--workspace',
728 help='Workspace directory to mount into the chroot.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900729 parser.add_argument('--goma_dir', type='path',
730 help='Goma installed directory to mount into the chroot.')
731 parser.add_argument('--goma_client_json', type='path',
732 help='Service account json file to use goma on bot. '
733 'Mounted into the chroot.')
Yong Hong84ba9172018-02-07 01:37:42 +0800734
735 # Use type=str instead of type='path' to prevent the given path from being
736 # transfered to absolute path automatically.
737 parser.add_argument('--working-dir', type=str,
738 help='Run the command in specific working directory in '
739 'chroot. If the given directory is a relative '
740 'path, this program will transfer the path to '
741 'the corresponding one inside chroot.')
742
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400743 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500744
745 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400746 group = parser.add_argument_group('Commands')
747 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500748 '--enter', action='store_true', default=False,
749 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400750 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500751 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500752 help='Create the chroot only if it does not already exist. '
753 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400754 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500755 '--bootstrap', action='store_true', default=False,
756 help='Build everything from scratch, including the sdk. '
757 'Use this only if you need to validate a change '
758 'that affects SDK creation itself (toolchain and '
759 'build are typically the only folk who need this). '
760 'Note this will quite heavily slow down the build. '
761 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400762 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500763 '-r', '--replace', action='store_true', default=False,
764 help='Replace an existing SDK chroot. Basically an alias '
765 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400766 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500767 '--delete', action='store_true', default=False,
768 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400769 group.add_argument(
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600770 '--unmount', action='store_true', default=False,
771 help='Unmount and clean up devices associated with the '
772 'SDK chroot if it exists. This does not delete the '
773 'backing image file, so the same chroot can be later '
774 're-mounted for reuse. To fully delete the chroot, use '
775 '--delete. This is primarily useful for working on '
776 'cros_sdk or the chroot setup; you should not need it '
777 'under normal circumstances.')
778 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500779 '--download', action='store_true', default=False,
780 help='Download the sdk.')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600781 group.add_argument(
782 '--snapshot-create', metavar='SNAPSHOT_NAME',
783 help='Create a snapshot of the chroot. Requires that the chroot was '
784 'created without the --nouse-image option.')
785 group.add_argument(
786 '--snapshot-restore', metavar='SNAPSHOT_NAME',
787 help='Restore the chroot to a previously created snapshot.')
788 group.add_argument(
789 '--snapshot-delete', metavar='SNAPSHOT_NAME',
790 help='Delete a previously created snapshot. Deleting a snapshot that '
791 'does not exist is not an error.')
792 group.add_argument(
793 '--snapshot-list', action='store_true', default=False,
794 help='List existing snapshots of the chroot and exit.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500795 commands = group
796
Mike Frysinger80dfce92014-04-21 10:58:53 -0400797 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400798 group = parser.add_argument_group('Namespaces')
799 group.add_argument('--proxy-sim', action='store_true', default=False,
800 help='Simulate a restrictive network requiring an outbound'
801 ' proxy.')
802 group.add_argument('--no-ns-pid', dest='ns_pid',
803 default=True, action='store_false',
804 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400805
Mike Frysinger34db8692013-11-11 14:54:08 -0500806 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400807 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500808 'Internal Chromium OS Build Team Options',
809 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400810 group.add_argument('--buildbot-log-version', default=False,
811 action='store_true',
812 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500813
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400814 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500815
816
817def main(argv):
818 conf = cros_build_lib.LoadKeyValueFile(
819 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
820 ignore_missing=True)
821 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
822 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
823 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400824 options = parser.parse_args(argv)
825 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800826
827 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500828 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800829
Brian Harring1790ac42012-09-23 08:53:33 -0700830 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700831 if host != 'x86_64':
Benjamin Gordon040a1162017-06-29 13:44:47 -0600832 cros_build_lib.Die(
Brian Harring1790ac42012-09-23 08:53:33 -0700833 "cros_sdk is currently only supported on x86_64; you're running"
834 " %s. Please find a x86_64 machine." % (host,))
835
Josh Triplett472a4182013-03-08 11:48:57 -0800836 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
837 if options.proxy_sim:
838 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600839 missing_image_tools = osutils.FindMissingBinaries(IMAGE_NEEDED_TOOLS)
Brian Harringb938c782012-02-29 15:14:38 -0800840
Benjamin Gordon040a1162017-06-29 13:44:47 -0600841 if (sdk_latest_version == '<unknown>' or
842 bootstrap_latest_version == '<unknown>'):
843 cros_build_lib.Die(
844 'No SDK version was found. '
845 'Are you in a Chromium source tree instead of Chromium OS?\n\n'
846 'Please change to a directory inside your Chromium OS source tree\n'
847 'and retry. If you need to setup a Chromium OS source tree, see\n'
848 ' http://www.chromium.org/chromium-os/developer-guide')
849
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600850 any_snapshot_operation = (options.snapshot_create or options.snapshot_restore
851 or options.snapshot_delete or options.snapshot_list)
852 if any_snapshot_operation and not options.use_image:
853 cros_build_lib.Die('Snapshot operations are not compatible with '
854 '--nouse-image.')
855
856 if (options.snapshot_delete and options.snapshot_delete ==
857 options.snapshot_restore):
858 parser.error('Cannot --snapshot_delete the same snapshot you are '
859 'restoring with --snapshot_restore.')
860
David James471532c2013-01-21 10:23:31 -0800861 _ReExecuteIfNeeded([sys.argv[0]] + argv)
862
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600863 lock_path = os.path.dirname(options.chroot)
864 lock_path = os.path.join(
865 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
866
Brian Harring218e13c2012-10-10 16:21:26 -0700867 # Expand out the aliases...
868 if options.replace:
869 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800870
Brian Harring218e13c2012-10-10 16:21:26 -0700871 if options.bootstrap:
872 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800873
Brian Harring218e13c2012-10-10 16:21:26 -0700874 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400875 # pylint: disable=protected-access
876 # This _group_actions access sucks, but upstream decided to not include an
877 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700878 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400879 for x in commands._group_actions)
880 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700881 options.enter |= bool(chroot_command)
882
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600883 if (options.delete and not options.create and
884 (options.enter or any_snapshot_operation)):
885 parser.error("Trying to enter or snapshot the chroot when --delete "
Brian Harring218e13c2012-10-10 16:21:26 -0700886 "was specified makes no sense.")
887
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600888 if (options.unmount and
889 (options.create or options.enter or any_snapshot_operation)):
890 parser.error('--unmount cannot be specified with other chroot actions.')
891
Yong Hong84ba9172018-02-07 01:37:42 +0800892 if options.working_dir is not None and not os.path.isabs(options.working_dir):
893 options.working_dir = path_util.ToChrootPath(options.working_dir)
894
Benjamin Gordon35194f12017-07-19 10:26:22 -0600895 # Discern if we need to create the chroot.
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600896 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600897 if (options.use_image and not chroot_exists and not options.delete and
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600898 not options.unmount and not missing_image_tools and
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600899 os.path.exists(_ImageFileForChroot(options.chroot))):
900 # Try to re-mount an existing image in case the user has rebooted.
901 with cgroups.SimpleContainChildren('cros_sdk'):
902 with locking.FileLock(lock_path, 'chroot lock') as lock:
903 logging.debug('Checking if existing chroot image can be mounted.')
904 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600905 cros_sdk_lib.MountChroot(options.chroot, create=False)
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600906 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600907 if chroot_exists:
908 logging.notice('Mounted existing image %s on chroot',
909 _ImageFileForChroot(options.chroot))
Brian Harring218e13c2012-10-10 16:21:26 -0700910
911 # Finally, flip create if necessary.
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600912 if options.enter or options.snapshot_create:
Brian Harring218e13c2012-10-10 16:21:26 -0700913 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800914
Benjamin Gordon7b44bef2018-06-08 08:13:59 -0600915 # Make sure we will download if we plan to create.
916 options.download |= options.create
917
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600918 # Anything that needs to manipulate the main chroot mount or communicate with
919 # LVM needs to be done here before we enter the new namespaces.
920
921 # If deleting, do it regardless of the use_image flag so that a
922 # previously-created loopback chroot can also be cleaned up.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600923 # TODO(bmgordon): See if the DeleteChroot call below can be removed in
924 # favor of this block.
925 chroot_deleted = False
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600926 if options.delete:
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600927 with cgroups.SimpleContainChildren('cros_sdk'):
928 with locking.FileLock(lock_path, 'chroot lock') as lock:
929 lock.write_lock()
930 if missing_image_tools:
931 logging.notice('Unmounting chroot.')
932 osutils.UmountTree(options.chroot)
933 else:
934 logging.notice('Deleting chroot.')
Don Garrett36650112018-06-28 15:54:34 -0700935 cros_sdk_lib.CleanupChrootMount(options.chroot, delete=True)
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600936 chroot_deleted = True
937
Benjamin Gordon64a3f8d2018-06-08 10:34:39 -0600938 # If cleanup was requested, we have to do it while we're still in the original
939 # namespace. Since cleaning up the mount will interfere with any other
940 # commands, we exit here. The check above should have made sure that no other
941 # action was requested, anyway.
942 if options.unmount:
943 with locking.FileLock(lock_path, 'chroot lock') as lock:
944 lock.write_lock()
945 CleanupChroot(options.chroot)
946 sys.exit(0)
947
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600948 # Make sure the main chroot mount is visible. Contents will be filled in
949 # below if needed.
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600950 if options.create and options.use_image:
951 if missing_image_tools:
952 raise SystemExit(
953 '''The tool(s) %s were not found.
954Please make sure the lvm2 and thin-provisioning-tools packages
955are installed on your host.
956Example(ubuntu):
957 sudo apt-get install lvm2 thin-provisioning-tools
958
959If you want to run without lvm2, pass --nouse-image (chroot
960snapshots will be unavailable).''' % ', '.join(missing_image_tools))
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600961
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600962 logging.debug('Making sure chroot image is mounted.')
963 with cgroups.SimpleContainChildren('cros_sdk'):
964 with locking.FileLock(lock_path, 'chroot lock') as lock:
965 lock.write_lock()
Benjamin Gordon74645232018-05-04 17:40:42 -0600966 if not cros_sdk_lib.MountChroot(options.chroot, create=True):
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600967 cros_build_lib.Die('Unable to mount %s on chroot',
968 _ImageFileForChroot(options.chroot))
969 logging.notice('Mounted %s on chroot',
970 _ImageFileForChroot(options.chroot))
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600971
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600972 # Snapshot operations will always need the VG/LV, but other actions won't.
973 if any_snapshot_operation:
974 with cgroups.SimpleContainChildren('cros_sdk'):
975 with locking.FileLock(lock_path, 'chroot lock') as lock:
Benjamin Gordon74645232018-05-04 17:40:42 -0600976 chroot_vg, chroot_lv = cros_sdk_lib.FindChrootMountSource(
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600977 options.chroot)
978 if not chroot_vg or not chroot_lv:
979 cros_build_lib.Die('Unable to find VG/LV for chroot %s',
980 options.chroot)
981
982 # Delete snapshot before creating a new one. This allows the user to
983 # throw out old state, create a new snapshot, and enter the chroot in a
984 # single call to cros_sdk. Since restore involves deleting, also do it
985 # before creating.
986 if options.snapshot_restore:
987 lock.write_lock()
988 valid_snapshots = ListChrootSnapshots(chroot_vg, chroot_lv)
989 if options.snapshot_restore not in valid_snapshots:
990 cros_build_lib.Die('%s is not a valid snapshot to restore to. '
991 'Valid snapshots: %s', options.snapshot_restore,
992 ', '.join(valid_snapshots))
993 osutils.UmountTree(options.chroot)
994 if not RestoreChrootSnapshot(options.snapshot_restore, chroot_vg,
995 chroot_lv):
996 cros_build_lib.Die('Unable to restore chroot to snapshot.')
Benjamin Gordon74645232018-05-04 17:40:42 -0600997 if not cros_sdk_lib.MountChroot(options.chroot, create=False):
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600998 cros_build_lib.Die('Unable to mount restored snapshot onto chroot.')
999
1000 # Use a read lock for snapshot delete and create even though they modify
1001 # the filesystem, because they don't modify the mounted chroot itself.
1002 # The underlying LVM commands take their own locks, so conflicting
1003 # concurrent operations here may crash cros_sdk, but won't corrupt the
1004 # chroot image. This tradeoff seems worth it to allow snapshot
1005 # operations on chroots that have a process inside.
1006 if options.snapshot_delete:
1007 lock.read_lock()
1008 DeleteChrootSnapshot(options.snapshot_delete, chroot_vg, chroot_lv)
1009
1010 if options.snapshot_create:
1011 lock.read_lock()
1012 if not CreateChrootSnapshot(options.snapshot_create, chroot_vg,
1013 chroot_lv):
1014 cros_build_lib.Die('Unable to create snapshot.')
1015
Benjamin Gordone3d5bd12017-11-16 15:42:28 -07001016 img_path = _ImageFileForChroot(options.chroot)
1017 if (options.use_image and os.path.exists(options.chroot) and
1018 os.path.exists(img_path)):
1019 img_stat = os.stat(img_path)
1020 img_used_bytes = img_stat.st_blocks * 512
1021
1022 mount_stat = os.statvfs(options.chroot)
1023 mount_used_bytes = mount_stat.f_frsize * (mount_stat.f_blocks -
1024 mount_stat.f_bfree)
1025
1026 extra_gbs = (img_used_bytes - mount_used_bytes) / 2**30
1027 if extra_gbs > MAX_UNUSED_IMAGE_GBS:
1028 logging.notice('%s is using %s GiB more than needed. Running '
1029 'fstrim.', img_path, extra_gbs)
1030 cmd = ['fstrim', options.chroot]
1031 try:
1032 cros_build_lib.RunCommand(cmd, print_cmd=False)
1033 except cros_build_lib.RunCommandError as e:
1034 logging.warning('Running fstrim failed. Consider running fstrim on '
1035 'your chroot manually.\nError: %s', e)
1036
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001037 # Enter a new set of namespaces. Everything after here cannot directly affect
1038 # the hosts's mounts or alter LVM volumes.
Benjamin Gordon386b9eb2017-07-20 09:21:33 -06001039 namespaces.SimpleUnshare()
1040 if options.ns_pid:
1041 first_pid = namespaces.CreatePidNs()
1042 else:
1043 first_pid = None
1044
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001045 if options.snapshot_list:
1046 for snap in ListChrootSnapshots(chroot_vg, chroot_lv):
1047 print(snap)
1048 sys.exit(0)
1049
Brian Harringb938c782012-02-29 15:14:38 -08001050 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -07001051 sdk_version = (bootstrap_latest_version if options.bootstrap
1052 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -08001053 else:
1054 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -05001055 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -07001056 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -08001057
Gilad Arnoldecc86fa2015-05-22 12:06:04 -07001058 # Based on selections, determine the tarball to fetch.
Yong Hong4e29b622018-02-05 14:31:10 +08001059 if options.download:
1060 if options.sdk_url:
1061 urls = [options.sdk_url]
1062 elif options.bootstrap:
1063 urls = GetStage3Urls(sdk_version)
1064 else:
1065 urls = GetArchStageTarballs(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -07001066
Mike Frysinger80dfce92014-04-21 10:58:53 -04001067 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -07001068 with locking.FileLock(lock_path, 'chroot lock') as lock:
Josh Triplett472a4182013-03-08 11:48:57 -08001069 if options.proxy_sim:
1070 _ProxySimSetup(options)
1071
Benjamin Gordonabb3e372017-08-09 10:21:05 -06001072 if (options.delete and not chroot_deleted and
1073 (os.path.exists(options.chroot) or
1074 os.path.exists(_ImageFileForChroot(options.chroot)))):
David James56e6c2c2012-10-24 23:54:41 -07001075 lock.write_lock()
1076 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -08001077
David James56e6c2c2012-10-24 23:54:41 -07001078 sdk_cache = os.path.join(options.cache_dir, 'sdks')
1079 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -07001080 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -07001081
David James56e6c2c2012-10-24 23:54:41 -07001082 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -05001083 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -07001084 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001085 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001086 continue
1087 lock.write_lock(
1088 "Upgrade to %r needed but chroot is locked; please exit "
1089 "all instances so this upgrade can finish." % src)
1090 if not os.path.exists(src):
1091 # Note that while waiting for the write lock, src may've vanished;
1092 # it's a rare race during the upgrade process that's a byproduct
1093 # of us avoiding taking a write lock to do the src check. If we
1094 # took a write lock for that check, it would effectively limit
1095 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -07001096 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -07001097 elif not os.path.exists(target):
1098 # Upgrade occurred, but a reversion, or something whacky
1099 # occurred writing to the old location. Wipe and continue.
1100 os.rename(src, target)
1101 else:
1102 # Upgrade occurred once already, but either a reversion or
1103 # some before/after separate cros_sdk usage is at play.
1104 # Wipe and continue.
1105 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -07001106
David James56e6c2c2012-10-24 23:54:41 -07001107 if options.download:
1108 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -07001109 sdk_tarball = FetchRemoteTarballs(
1110 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
Brian Harring218e13c2012-10-10 16:21:26 -07001111
David James56e6c2c2012-10-24 23:54:41 -07001112 if options.create:
1113 lock.write_lock()
Benjamin Gordon7b44bef2018-06-08 08:13:59 -06001114 # Recheck if the chroot is set up here before creating to make sure we
1115 # account for whatever the various delete/unmount/remount steps above
1116 # have done.
1117 if cros_sdk_lib.IsChrootReady(options.chroot):
1118 logging.debug('Chroot already exists. Skipping creation.')
1119 else:
1120 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
1121 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -07001122
David James56e6c2c2012-10-24 23:54:41 -07001123 if options.enter:
1124 lock.read_lock()
1125 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -07001126 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +09001127 options.goma_dir, options.goma_client_json,
Yong Hong84ba9172018-02-07 01:37:42 +08001128 options.working_dir, chroot_command)