blob: f813cf1d6d2f348f651b4616bbc478d306c8af8a [file] [log] [blame]
Mike Frysinger2de7f042012-07-10 04:45:03 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysinger2f95cfc2015-06-04 04:00:26 -04005"""Manage SDK chroots.
6
7This script is used for manipulating local chroot environments; creating,
8deleting, downloading, etc. If given --enter (or no args), it defaults
9to an interactive bash shell within the chroot.
10
11If given args those are passed to the chroot environment, and executed.
12"""
Brian Harringb938c782012-02-29 15:14:38 -080013
Mike Frysinger383367e2014-09-16 15:06:17 -040014from __future__ import print_function
15
Mike Frysinger2f95cfc2015-06-04 04:00:26 -040016import argparse
Josh Triplett472a4182013-03-08 11:48:57 -080017import glob
Brian Harringb938c782012-02-29 15:14:38 -080018import os
Josh Triplett472a4182013-03-08 11:48:57 -080019import pwd
Brian Norrisd37e2f72016-08-22 16:09:24 -070020import re
David James56e6c2c2012-10-24 23:54:41 -070021import sys
Brian Harringb938c782012-02-29 15:14:38 -080022import urlparse
23
Aviv Keshetb7519e12016-10-04 00:50:00 -070024from chromite.lib import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080025from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070026from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080027from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070028from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080029from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080030from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070031from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040032from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080033from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050034from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080035
36cros_build_lib.STRICT_SUDO = True
37
38
Zdenek Behanaa52cea2012-05-30 01:31:11 +020039COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020040
Brian Harringb938c782012-02-29 15:14:38 -080041# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050042MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
43 'src/scripts/sdk_lib/make_chroot.sh')]
44ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
45 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080046
Josh Triplett472a4182013-03-08 11:48:57 -080047# Proxy simulator configuration.
48PROXY_HOST_IP = '192.168.240.1'
49PROXY_PORT = 8080
50PROXY_GUEST_IP = '192.168.240.2'
51PROXY_NETMASK = 30
52PROXY_VETH_PREFIX = 'veth'
53PROXY_CONNECT_PORTS = (80, 443, 9418)
54PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
55PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
56PROXY_APACHE_FALLBACK_PATH = ':'.join(
57 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
58)
59PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
60
Josh Triplett9a495f62013-03-15 18:06:55 -070061# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080062NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080063
Josh Triplett472a4182013-03-08 11:48:57 -080064# Tools needed for --proxy-sim only.
65PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080066
Mike Frysingercc838832014-05-24 13:10:30 -040067
Brian Harring1790ac42012-09-23 08:53:33 -070068def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080069 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070070 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050071 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
72 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070073 for compressor in COMPRESSION_PREFERENCE]
74
75
76def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050077 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070078 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080079
80
Gilad Arnold6a8f0452015-06-04 11:25:18 -070081def GetToolchainsOverlayUrls(version, toolchains):
82 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070083
84 Args:
85 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
86 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -070087 overlays are stored (.../2015/05/ in this case).
88 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070089
90 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -070091 List of alternative download URLs for an SDK overlay tarball that contains
92 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070093 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -070094 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070095 suburl_template = os.path.join(
96 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -070097 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
98 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070099 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
100 for ext in COMPRESSION_PREFERENCE]
101
102
103def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500104 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200105
106 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500107 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200108 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700109 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700110 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111
112 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700113 Full path to the downloaded file, or None if |allow_none| and no URL worked.
114
115 Raises:
116 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200117 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200118
Brian Harring1790ac42012-09-23 08:53:33 -0700119 # Note we track content length ourselves since certain versions of curl
120 # fail if asked to resume a complete file.
121 # pylint: disable=C0301,W0631
122 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700123 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700124 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200125 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700126 # http://www.logilab.org/ticket/8766
127 # pylint: disable=E1101
128 parsed = urlparse.urlparse(url)
129 tarball_name = os.path.basename(parsed.path)
130 if parsed.scheme in ('', 'file'):
131 if os.path.exists(parsed.path):
132 return parsed.path
133 continue
134 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700135 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800136 result = retry_util.RunCurl(
Ralph Nathan0d7dbf32015-06-08 17:06:13 -0700137 ['-I', url], fail=False, capture_output=False, redirect_stdout=True,
138 redirect_stderr=True, print_cmd=False, debug_level=logging.NOTICE)
Brian Harring1790ac42012-09-23 08:53:33 -0700139 successful = False
140 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700141 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700142 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700143 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700144 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500145 elif header.lower().startswith('content-length:'):
146 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700147 if successful:
148 break
149 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200150 break
151 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700152 if allow_none:
153 return None
154 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200155
Brian Harringae0a5322012-09-15 01:46:51 -0700156 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700157 current_size = 0
158 if os.path.exists(tarball_dest):
159 current_size = os.path.getsize(tarball_dest)
160 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700161 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700162 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100163
Brian Harring1790ac42012-09-23 08:53:33 -0700164 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800165 retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400166 ['-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Ralph Nathan0d7dbf32015-06-08 17:06:13 -0700167 print_cmd=False, capture_output=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800168
Brian Harring1790ac42012-09-23 08:53:33 -0700169 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700170 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
171 # because we might have partial overlap between known prefixes.
172 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
173 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
174 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700175 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700176 if (filename == tarball_name or
177 any([(filename.startswith(p) and
178 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
179 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700180 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700181 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700182 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200183
Brian Harringb938c782012-02-29 15:14:38 -0800184 return tarball_dest
185
186
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700187def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
David Pursell3528e8e2015-07-07 11:22:15 -0700188 cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800189 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800190
Brian Harring1790ac42012-09-23 08:53:33 -0700191 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700192 '--chroot', chroot_path,
193 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700194
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700195 if toolchains_overlay_tarball:
196 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
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
Brian Harringae0a5322012-09-15 01:46:51 -0700219def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700220 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800221 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400222 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
223 # The os.ST_NOSUID constant wasn't added until python-3.2.
224 if st.f_flag & 0x2:
225 cros_build_lib.Die('chroot cannot be in a nosuid mount')
226
Brian Harringae0a5322012-09-15 01:46:51 -0700227 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800228 if chrome_root:
229 cmd.extend(['--chrome_root', chrome_root])
230 if chrome_root_mount:
231 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700232 if workspace:
233 cmd.extend(['--workspace_root', workspace])
234
Brian Harringb938c782012-02-29 15:14:38 -0800235 if len(additional_args) > 0:
236 cmd.append('--')
237 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700238
Ralph Nathan549d3502015-03-26 17:38:42 -0700239 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
240 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700241 # If we were in interactive mode, ignore the exit code; it'll be whatever
242 # they last ran w/in the chroot and won't matter to us one way or another.
243 # Note this does allow chroot entrance to fail and be ignored during
244 # interactive; this is however a rare case and the user will immediately
245 # see it (nor will they be checking the exit code manually).
246 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700247 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800248
249
David James56e6c2c2012-10-24 23:54:41 -0700250def _SudoCommand():
251 """Get the 'sudo' command, along with all needed environment variables."""
252
David James5a73b4d2013-03-07 10:23:40 -0800253 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
254 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700255 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800256 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700257 value = os.environ.get(key)
258 if value is not None:
259 cmd += ['%s=%s' % (key, value)]
260
261 # Pass in the path to the depot_tools so that users can access them from
262 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400263 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500264
David James56e6c2c2012-10-24 23:54:41 -0700265 return cmd
266
267
Josh Triplett472a4182013-03-08 11:48:57 -0800268def _ReportMissing(missing):
269 """Report missing utilities, then exit.
270
271 Args:
272 missing: List of missing utilities, as returned by
273 osutils.FindMissingBinaries. If non-empty, will not return.
274 """
275
276 if missing:
277 raise SystemExit(
278 'The tool(s) %s were not found.\n'
279 'Please install the appropriate package in your host.\n'
280 'Example(ubuntu):\n'
281 ' sudo apt-get install <packagename>'
282 % ', '.join(missing))
283
284
285def _ProxySimSetup(options):
286 """Set up proxy simulator, and return only in the child environment.
287
288 TODO: Ideally, this should support multiple concurrent invocations of
289 cros_sdk --proxy-sim; currently, such invocations will conflict with each
290 other due to the veth device names and IP addresses. Either this code would
291 need to generate fresh, unused names for all of these before forking, or it
292 would need to support multiple concurrent cros_sdk invocations sharing one
293 proxy and allowing it to exit when unused (without counting on any local
294 service-management infrastructure on the host).
295 """
296
297 may_need_mpm = False
298 apache_bin = osutils.Which('apache2')
299 if apache_bin is None:
300 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
301 if apache_bin is None:
302 _ReportMissing(('apache2',))
303 else:
304 may_need_mpm = True
305
306 # Module names and .so names included for ease of grepping.
307 apache_modules = [('proxy_module', 'mod_proxy.so'),
308 ('proxy_connect_module', 'mod_proxy_connect.so'),
309 ('proxy_http_module', 'mod_proxy_http.so'),
310 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
311
312 # Find the apache module directory, and make sure it has the modules we need.
313 module_dirs = {}
314 for g in PROXY_APACHE_MODULE_GLOBS:
315 for mod, so in apache_modules:
316 for f in glob.glob(os.path.join(g, so)):
317 module_dirs.setdefault(os.path.dirname(f), []).append(so)
318 for apache_module_path, modules_found in module_dirs.iteritems():
319 if len(modules_found) == len(apache_modules):
320 break
321 else:
322 # Appease cros lint, which doesn't understand that this else block will not
323 # fall through to the subsequent code which relies on apache_module_path.
324 apache_module_path = None
325 raise SystemExit(
326 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500327 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800328
329 def check_add_module(name):
330 so = 'mod_%s.so' % name
331 if os.access(os.path.join(apache_module_path, so), os.F_OK):
332 mod = '%s_module' % name
333 apache_modules.append((mod, so))
334 return True
335 return False
336
337 check_add_module('authz_core')
338 if may_need_mpm:
339 for mpm in PROXY_APACHE_MPMS:
340 if check_add_module('mpm_%s' % mpm):
341 break
342
343 veth_host = '%s-host' % PROXY_VETH_PREFIX
344 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
345
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500346 # Set up locks to sync the net namespace setup. We need the child to create
347 # the net ns first, and then have the parent assign the guest end of the veth
348 # interface to the child's new network namespace & bring up the proxy. Only
349 # then can the child move forward and rely on the network being up.
350 ns_create_lock = locking.PipeLock()
351 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800352
353 pid = os.fork()
354 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500355 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800356 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500357
358 # Signal the parent the ns is ready to be configured.
359 ns_create_lock.Post()
360 del ns_create_lock
361
362 # Wait for the parent to finish setting up the ns/proxy.
363 ns_setup_lock.Wait()
364 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800365
366 # Set up child side of the network.
367 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500368 ('ip', 'link', 'set', 'up', 'lo'),
369 ('ip', 'address', 'add',
370 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
371 'dev', veth_guest),
372 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800373 )
374 try:
375 for cmd in commands:
376 cros_build_lib.RunCommand(cmd, print_cmd=False)
377 except cros_build_lib.RunCommandError:
378 raise SystemExit('Running %r failed!' % (cmd,))
379
380 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
381 for proto in ('http', 'https', 'ftp'):
382 os.environ[proto + '_proxy'] = proxy_url
383 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
384 os.environ.pop(v, None)
385 return
386
Josh Triplett472a4182013-03-08 11:48:57 -0800387 # Set up parent side of the network.
388 uid = int(os.environ.get('SUDO_UID', '0'))
389 gid = int(os.environ.get('SUDO_GID', '0'))
390 if uid == 0 or gid == 0:
391 for username in PROXY_APACHE_FALLBACK_USERS:
392 try:
393 pwnam = pwd.getpwnam(username)
394 uid, gid = pwnam.pw_uid, pwnam.pw_gid
395 break
396 except KeyError:
397 continue
398 if uid == 0 or gid == 0:
399 raise SystemExit('Could not find a non-root user to run Apache as')
400
401 chroot_parent, chroot_base = os.path.split(options.chroot)
402 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
403 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
404
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500405 # Wait for the child to create the net ns.
406 ns_create_lock.Wait()
407 del ns_create_lock
408
Josh Triplett472a4182013-03-08 11:48:57 -0800409 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500410 'User #%u' % uid,
411 'Group #%u' % gid,
412 'PidFile %s' % pid_file,
413 'ErrorLog %s' % log_file,
414 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
415 'ServerName %s' % PROXY_HOST_IP,
416 'ProxyRequests On',
417 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800418 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500419 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
420 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800421 ]
422 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500423 ('ip', 'link', 'add', 'name', veth_host,
424 'type', 'veth', 'peer', 'name', veth_guest),
425 ('ip', 'address', 'add',
426 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
427 'dev', veth_host),
428 ('ip', 'link', 'set', veth_host, 'up'),
429 ([apache_bin, '-f', '/dev/null'] +
430 [arg for d in apache_directives for arg in ('-C', d)]),
431 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800432 )
433 cmd = None # Make cros lint happy.
434 try:
435 for cmd in commands:
436 cros_build_lib.RunCommand(cmd, print_cmd=False)
437 except cros_build_lib.RunCommandError:
438 # Clean up existing interfaces, if any.
439 cmd_cleanup = ('ip', 'link', 'del', veth_host)
440 try:
441 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
442 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700443 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800444 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500445
446 # Signal the child that the net ns/proxy is fully configured now.
447 ns_setup_lock.Post()
448 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800449
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400450 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800451
452
Mike Frysingera78a56e2012-11-20 06:02:30 -0500453def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700454 """Re-execute cros_sdk as root.
455
456 Also unshare the mount namespace so as to ensure that processes outside
457 the chroot can't mess with our mounts.
458 """
459 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500460 cmd = _SudoCommand() + ['--'] + argv
461 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500462 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400463 # We must set up the cgroups mounts before we enter our own namespace.
464 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800465 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500466 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700467
468
Mike Frysinger34db8692013-11-11 14:54:08 -0500469def _CreateParser(sdk_latest_version, bootstrap_latest_version):
470 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400471 usage = ('usage: %(prog)s [options] '
472 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
473 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
474 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700475
Mike Frysinger34db8692013-11-11 14:54:08 -0500476 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500477 default_chroot = os.path.join(constants.SOURCE_ROOT,
478 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400479 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700480 '--chroot', dest='chroot', default=default_chroot, type='path',
481 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800482
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400483 parser.add_argument('--chrome_root', type='path',
484 help='Mount this chrome root into the SDK chroot')
485 parser.add_argument('--chrome_root_mount', type='path',
486 help='Mount chrome into this path inside SDK chroot')
487 parser.add_argument('--nousepkg', action='store_true', default=False,
488 help='Do not use binary packages when creating a chroot.')
489 parser.add_argument('-u', '--url', dest='sdk_url',
490 help='Use sdk tarball located at this url. Use file:// '
491 'for local files.')
492 parser.add_argument('--sdk-version',
493 help=('Use this sdk version. For prebuilt, current is %r'
494 ', for bootstrapping it is %r.'
495 % (sdk_latest_version, bootstrap_latest_version)))
496 parser.add_argument('--workspace',
497 help='Workspace directory to mount into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400498 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500499
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700500 # SDK overlay tarball options (mutually exclusive).
501 group = parser.add_mutually_exclusive_group()
502 group.add_argument('--toolchains',
503 help=('Comma-separated list of toolchains we expect to be '
504 'using on the chroot. Used for downloading a '
505 'corresponding SDK toolchains group (if one is '
506 'found), which may speed up chroot initialization '
507 'when building for the first time. Otherwise this '
508 'has no effect and will not restrict the chroot in '
509 'any way. Ignored if using --bootstrap.'))
510 group.add_argument('--board',
511 help=('The board we intend to be building in the chroot. '
512 'Used for deriving the list of required toolchains '
513 '(see --toolchains).'))
514
Mike Frysinger34db8692013-11-11 14:54:08 -0500515 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400516 group = parser.add_argument_group('Commands')
517 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500518 '--enter', action='store_true', default=False,
519 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400520 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500521 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500522 help='Create the chroot only if it does not already exist. '
523 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400524 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500525 '--bootstrap', action='store_true', default=False,
526 help='Build everything from scratch, including the sdk. '
527 'Use this only if you need to validate a change '
528 'that affects SDK creation itself (toolchain and '
529 'build are typically the only folk who need this). '
530 'Note this will quite heavily slow down the build. '
531 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400532 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500533 '-r', '--replace', action='store_true', default=False,
534 help='Replace an existing SDK chroot. Basically an alias '
535 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400536 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500537 '--delete', action='store_true', default=False,
538 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400539 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500540 '--download', action='store_true', default=False,
541 help='Download the sdk.')
542 commands = group
543
Mike Frysinger80dfce92014-04-21 10:58:53 -0400544 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400545 group = parser.add_argument_group('Namespaces')
546 group.add_argument('--proxy-sim', action='store_true', default=False,
547 help='Simulate a restrictive network requiring an outbound'
548 ' proxy.')
549 group.add_argument('--no-ns-pid', dest='ns_pid',
550 default=True, action='store_false',
551 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400552
Mike Frysinger34db8692013-11-11 14:54:08 -0500553 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400554 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500555 'Internal Chromium OS Build Team Options',
556 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400557 group.add_argument('--buildbot-log-version', default=False,
558 action='store_true',
559 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500560
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400561 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500562
563
564def main(argv):
565 conf = cros_build_lib.LoadKeyValueFile(
566 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
567 ignore_missing=True)
568 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
569 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
570 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400571 options = parser.parse_args(argv)
572 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800573
574 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500575 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800576
Brian Harring1790ac42012-09-23 08:53:33 -0700577 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700578 if host != 'x86_64':
579 parser.error(
580 "cros_sdk is currently only supported on x86_64; you're running"
581 " %s. Please find a x86_64 machine." % (host,))
582
Josh Triplett472a4182013-03-08 11:48:57 -0800583 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
584 if options.proxy_sim:
585 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800586
David James471532c2013-01-21 10:23:31 -0800587 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400588 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400589 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400590 else:
591 first_pid = None
David James471532c2013-01-21 10:23:31 -0800592
Brian Harring218e13c2012-10-10 16:21:26 -0700593 # Expand out the aliases...
594 if options.replace:
595 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800596
Brian Harring218e13c2012-10-10 16:21:26 -0700597 if options.bootstrap:
598 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800599
Brian Harring218e13c2012-10-10 16:21:26 -0700600 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400601 # pylint: disable=protected-access
602 # This _group_actions access sucks, but upstream decided to not include an
603 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700604 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400605 for x in commands._group_actions)
606 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700607 options.enter |= bool(chroot_command)
608
609 if options.enter and options.delete and not options.create:
610 parser.error("Trying to enter the chroot when --delete "
611 "was specified makes no sense.")
612
613 # Finally, discern if we need to create the chroot.
614 chroot_exists = os.path.exists(options.chroot)
615 if options.create or options.enter:
616 # Only create if it's being wiped, or if it doesn't exist.
617 if not options.delete and chroot_exists:
618 options.create = False
619 else:
620 options.download = True
621
622 # Finally, flip create if necessary.
623 if options.enter:
624 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800625
Brian Harringb938c782012-02-29 15:14:38 -0800626 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700627 sdk_version = (bootstrap_latest_version if options.bootstrap
628 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800629 else:
630 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500631 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700632 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800633
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700634 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700635 if options.sdk_url:
636 urls = [options.sdk_url]
637 elif options.bootstrap:
638 urls = GetStage3Urls(sdk_version)
639 else:
640 urls = GetArchStageTarballs(sdk_version)
641
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700642 # Get URLs for the toolchains overlay, if one is to be used.
643 toolchains_overlay_urls = None
644 if not options.bootstrap:
645 toolchains = None
646 if options.toolchains:
647 toolchains = options.toolchains.split(',')
648 elif options.board:
649 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
650
651 if toolchains:
652 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
653 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700654
Brian Harringb6cf9142012-09-01 20:43:17 -0700655 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700656 lock_path = os.path.join(
657 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400658 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700659 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700660 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700661
Josh Triplett472a4182013-03-08 11:48:57 -0800662 if options.proxy_sim:
663 _ProxySimSetup(options)
664
David James56e6c2c2012-10-24 23:54:41 -0700665 if options.delete and os.path.exists(options.chroot):
666 lock.write_lock()
667 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800668
David James56e6c2c2012-10-24 23:54:41 -0700669 sdk_cache = os.path.join(options.cache_dir, 'sdks')
670 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700671 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700672
David James56e6c2c2012-10-24 23:54:41 -0700673 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500674 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700675 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -0700676 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -0700677 continue
678 lock.write_lock(
679 "Upgrade to %r needed but chroot is locked; please exit "
680 "all instances so this upgrade can finish." % src)
681 if not os.path.exists(src):
682 # Note that while waiting for the write lock, src may've vanished;
683 # it's a rare race during the upgrade process that's a byproduct
684 # of us avoiding taking a write lock to do the src check. If we
685 # took a write lock for that check, it would effectively limit
686 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -0700687 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -0700688 elif not os.path.exists(target):
689 # Upgrade occurred, but a reversion, or something whacky
690 # occurred writing to the old location. Wipe and continue.
691 os.rename(src, target)
692 else:
693 # Upgrade occurred once already, but either a reversion or
694 # some before/after separate cros_sdk usage is at play.
695 # Wipe and continue.
696 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700697
David James56e6c2c2012-10-24 23:54:41 -0700698 if options.download:
699 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700700 sdk_tarball = FetchRemoteTarballs(
701 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
702 if toolchains_overlay_urls:
703 toolchains_overlay_tarball = FetchRemoteTarballs(
704 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
705 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700706
David James56e6c2c2012-10-24 23:54:41 -0700707 if options.create:
708 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700709 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700710 options.cache_dir,
David Pursell3528e8e2015-07-07 11:22:15 -0700711 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700712
David James56e6c2c2012-10-24 23:54:41 -0700713 if options.enter:
714 lock.read_lock()
715 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700716 options.chrome_root_mount, options.workspace,
717 chroot_command)