blob: a50ee20b75fa08cd1ab16e5f31b8755617848244 [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
David James56e6c2c2012-10-24 23:54:41 -070020import sys
Brian Harringb938c782012-02-29 15:14:38 -080021import urlparse
22
Don Garrett88b8d782014-05-13 17:30:55 -070023from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080024from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070025from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080026from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070027from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080028from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080029from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070030from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040031from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080032from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050033from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080034
35cros_build_lib.STRICT_SUDO = True
36
37
Zdenek Behanaa52cea2012-05-30 01:31:11 +020038COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020039
Brian Harringb938c782012-02-29 15:14:38 -080040# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050041MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
42 'src/scripts/sdk_lib/make_chroot.sh')]
43ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
44 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080045
Josh Triplett472a4182013-03-08 11:48:57 -080046# Proxy simulator configuration.
47PROXY_HOST_IP = '192.168.240.1'
48PROXY_PORT = 8080
49PROXY_GUEST_IP = '192.168.240.2'
50PROXY_NETMASK = 30
51PROXY_VETH_PREFIX = 'veth'
52PROXY_CONNECT_PORTS = (80, 443, 9418)
53PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
54PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
55PROXY_APACHE_FALLBACK_PATH = ':'.join(
56 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
57)
58PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
59
Josh Triplett9a495f62013-03-15 18:06:55 -070060# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080061NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080062
Josh Triplett472a4182013-03-08 11:48:57 -080063# Tools needed for --proxy-sim only.
64PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080065
Mike Frysingercc838832014-05-24 13:10:30 -040066
Brian Harring1790ac42012-09-23 08:53:33 -070067def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080068 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070069 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050070 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
71 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070072 for compressor in COMPRESSION_PREFERENCE]
73
74
75def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050076 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070077 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080078
79
Gilad Arnold6a8f0452015-06-04 11:25:18 -070080def GetToolchainsOverlayUrls(version, toolchains):
81 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070082
83 Args:
84 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
85 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -070086 overlays are stored (.../2015/05/ in this case).
87 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070088
89 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -070090 List of alternative download URLs for an SDK overlay tarball that contains
91 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070092 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -070093 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070094 suburl_template = os.path.join(
95 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -070096 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
97 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070098 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
99 for ext in COMPRESSION_PREFERENCE]
100
101
102def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500103 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200104
105 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500106 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200107 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700108 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700109 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200110
111 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700112 Full path to the downloaded file, or None if |allow_none| and no URL worked.
113
114 Raises:
115 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200116 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200117
Brian Harring1790ac42012-09-23 08:53:33 -0700118 # Note we track content length ourselves since certain versions of curl
119 # fail if asked to resume a complete file.
120 # pylint: disable=C0301,W0631
121 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700122 logging.notice('Downloading %s tarball...', desc)
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200123 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700124 # http://www.logilab.org/ticket/8766
125 # pylint: disable=E1101
126 parsed = urlparse.urlparse(url)
127 tarball_name = os.path.basename(parsed.path)
128 if parsed.scheme in ('', 'file'):
129 if os.path.exists(parsed.path):
130 return parsed.path
131 continue
132 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700133 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800134 result = retry_util.RunCurl(
Ralph Nathan0d7dbf32015-06-08 17:06:13 -0700135 ['-I', url], fail=False, capture_output=False, redirect_stdout=True,
136 redirect_stderr=True, print_cmd=False, debug_level=logging.NOTICE)
Brian Harring1790ac42012-09-23 08:53:33 -0700137 successful = False
138 for header in result.output.splitlines():
139 # We must walk the output to find the string '200 OK' for use cases where
140 # a proxy is involved and may have pushed down the actual header.
141 if header.find('200 OK') != -1:
142 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500143 elif header.lower().startswith('content-length:'):
144 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700145 if successful:
146 break
147 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200148 break
149 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700150 if allow_none:
151 return None
152 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200153
Brian Harringae0a5322012-09-15 01:46:51 -0700154 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700155 current_size = 0
156 if os.path.exists(tarball_dest):
157 current_size = os.path.getsize(tarball_dest)
158 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700159 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700160 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100161
Brian Harring1790ac42012-09-23 08:53:33 -0700162 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800163 retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400164 ['-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Ralph Nathan0d7dbf32015-06-08 17:06:13 -0700165 print_cmd=False, capture_output=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800166
Brian Harring1790ac42012-09-23 08:53:33 -0700167 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700168 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
169 # because we might have partial overlap between known prefixes.
170 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
171 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
172 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700173 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700174 if (filename == tarball_name or
175 any([(filename.startswith(p) and
176 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
177 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700178 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700179 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700180 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200181
Brian Harringb938c782012-02-29 15:14:38 -0800182 return tarball_dest
183
184
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700185def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
David Pursell3528e8e2015-07-07 11:22:15 -0700186 cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800187 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800188
Brian Harring1790ac42012-09-23 08:53:33 -0700189 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700190 '--chroot', chroot_path,
191 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700192
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700193 if toolchains_overlay_tarball:
194 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700195
Mike Frysinger2de7f042012-07-10 04:45:03 -0400196 if nousepkg:
197 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800198
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700199 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800200 try:
201 cros_build_lib.RunCommand(cmd, print_cmd=False)
202 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700203 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800204
205
206def DeleteChroot(chroot_path):
207 """Deletes an existing chroot"""
208 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
209 '--delete']
210 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700211 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800212 cros_build_lib.RunCommand(cmd, print_cmd=False)
213 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700214 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800215
216
Brian Harringae0a5322012-09-15 01:46:51 -0700217def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700218 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800219 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400220 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
221 # The os.ST_NOSUID constant wasn't added until python-3.2.
222 if st.f_flag & 0x2:
223 cros_build_lib.Die('chroot cannot be in a nosuid mount')
224
Brian Harringae0a5322012-09-15 01:46:51 -0700225 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800226 if chrome_root:
227 cmd.extend(['--chrome_root', chrome_root])
228 if chrome_root_mount:
229 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700230 if workspace:
231 cmd.extend(['--workspace_root', workspace])
232
Brian Harringb938c782012-02-29 15:14:38 -0800233 if len(additional_args) > 0:
234 cmd.append('--')
235 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700236
Ralph Nathan549d3502015-03-26 17:38:42 -0700237 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
238 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700239 # If we were in interactive mode, ignore the exit code; it'll be whatever
240 # they last ran w/in the chroot and won't matter to us one way or another.
241 # Note this does allow chroot entrance to fail and be ignored during
242 # interactive; this is however a rare case and the user will immediately
243 # see it (nor will they be checking the exit code manually).
244 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700245 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800246
247
David James56e6c2c2012-10-24 23:54:41 -0700248def _SudoCommand():
249 """Get the 'sudo' command, along with all needed environment variables."""
250
David James5a73b4d2013-03-07 10:23:40 -0800251 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
252 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700253 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800254 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700255 value = os.environ.get(key)
256 if value is not None:
257 cmd += ['%s=%s' % (key, value)]
258
259 # Pass in the path to the depot_tools so that users can access them from
260 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400261 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500262
David James56e6c2c2012-10-24 23:54:41 -0700263 return cmd
264
265
Josh Triplett472a4182013-03-08 11:48:57 -0800266def _ReportMissing(missing):
267 """Report missing utilities, then exit.
268
269 Args:
270 missing: List of missing utilities, as returned by
271 osutils.FindMissingBinaries. If non-empty, will not return.
272 """
273
274 if missing:
275 raise SystemExit(
276 'The tool(s) %s were not found.\n'
277 'Please install the appropriate package in your host.\n'
278 'Example(ubuntu):\n'
279 ' sudo apt-get install <packagename>'
280 % ', '.join(missing))
281
282
283def _ProxySimSetup(options):
284 """Set up proxy simulator, and return only in the child environment.
285
286 TODO: Ideally, this should support multiple concurrent invocations of
287 cros_sdk --proxy-sim; currently, such invocations will conflict with each
288 other due to the veth device names and IP addresses. Either this code would
289 need to generate fresh, unused names for all of these before forking, or it
290 would need to support multiple concurrent cros_sdk invocations sharing one
291 proxy and allowing it to exit when unused (without counting on any local
292 service-management infrastructure on the host).
293 """
294
295 may_need_mpm = False
296 apache_bin = osutils.Which('apache2')
297 if apache_bin is None:
298 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
299 if apache_bin is None:
300 _ReportMissing(('apache2',))
301 else:
302 may_need_mpm = True
303
304 # Module names and .so names included for ease of grepping.
305 apache_modules = [('proxy_module', 'mod_proxy.so'),
306 ('proxy_connect_module', 'mod_proxy_connect.so'),
307 ('proxy_http_module', 'mod_proxy_http.so'),
308 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
309
310 # Find the apache module directory, and make sure it has the modules we need.
311 module_dirs = {}
312 for g in PROXY_APACHE_MODULE_GLOBS:
313 for mod, so in apache_modules:
314 for f in glob.glob(os.path.join(g, so)):
315 module_dirs.setdefault(os.path.dirname(f), []).append(so)
316 for apache_module_path, modules_found in module_dirs.iteritems():
317 if len(modules_found) == len(apache_modules):
318 break
319 else:
320 # Appease cros lint, which doesn't understand that this else block will not
321 # fall through to the subsequent code which relies on apache_module_path.
322 apache_module_path = None
323 raise SystemExit(
324 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500325 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800326
327 def check_add_module(name):
328 so = 'mod_%s.so' % name
329 if os.access(os.path.join(apache_module_path, so), os.F_OK):
330 mod = '%s_module' % name
331 apache_modules.append((mod, so))
332 return True
333 return False
334
335 check_add_module('authz_core')
336 if may_need_mpm:
337 for mpm in PROXY_APACHE_MPMS:
338 if check_add_module('mpm_%s' % mpm):
339 break
340
341 veth_host = '%s-host' % PROXY_VETH_PREFIX
342 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
343
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500344 # Set up locks to sync the net namespace setup. We need the child to create
345 # the net ns first, and then have the parent assign the guest end of the veth
346 # interface to the child's new network namespace & bring up the proxy. Only
347 # then can the child move forward and rely on the network being up.
348 ns_create_lock = locking.PipeLock()
349 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800350
351 pid = os.fork()
352 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500353 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800354 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500355
356 # Signal the parent the ns is ready to be configured.
357 ns_create_lock.Post()
358 del ns_create_lock
359
360 # Wait for the parent to finish setting up the ns/proxy.
361 ns_setup_lock.Wait()
362 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800363
364 # Set up child side of the network.
365 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500366 ('ip', 'link', 'set', 'up', 'lo'),
367 ('ip', 'address', 'add',
368 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
369 'dev', veth_guest),
370 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800371 )
372 try:
373 for cmd in commands:
374 cros_build_lib.RunCommand(cmd, print_cmd=False)
375 except cros_build_lib.RunCommandError:
376 raise SystemExit('Running %r failed!' % (cmd,))
377
378 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
379 for proto in ('http', 'https', 'ftp'):
380 os.environ[proto + '_proxy'] = proxy_url
381 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
382 os.environ.pop(v, None)
383 return
384
Josh Triplett472a4182013-03-08 11:48:57 -0800385 # Set up parent side of the network.
386 uid = int(os.environ.get('SUDO_UID', '0'))
387 gid = int(os.environ.get('SUDO_GID', '0'))
388 if uid == 0 or gid == 0:
389 for username in PROXY_APACHE_FALLBACK_USERS:
390 try:
391 pwnam = pwd.getpwnam(username)
392 uid, gid = pwnam.pw_uid, pwnam.pw_gid
393 break
394 except KeyError:
395 continue
396 if uid == 0 or gid == 0:
397 raise SystemExit('Could not find a non-root user to run Apache as')
398
399 chroot_parent, chroot_base = os.path.split(options.chroot)
400 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
401 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
402
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500403 # Wait for the child to create the net ns.
404 ns_create_lock.Wait()
405 del ns_create_lock
406
Josh Triplett472a4182013-03-08 11:48:57 -0800407 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500408 'User #%u' % uid,
409 'Group #%u' % gid,
410 'PidFile %s' % pid_file,
411 'ErrorLog %s' % log_file,
412 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
413 'ServerName %s' % PROXY_HOST_IP,
414 'ProxyRequests On',
415 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800416 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500417 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
418 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800419 ]
420 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500421 ('ip', 'link', 'add', 'name', veth_host,
422 'type', 'veth', 'peer', 'name', veth_guest),
423 ('ip', 'address', 'add',
424 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
425 'dev', veth_host),
426 ('ip', 'link', 'set', veth_host, 'up'),
427 ([apache_bin, '-f', '/dev/null'] +
428 [arg for d in apache_directives for arg in ('-C', d)]),
429 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800430 )
431 cmd = None # Make cros lint happy.
432 try:
433 for cmd in commands:
434 cros_build_lib.RunCommand(cmd, print_cmd=False)
435 except cros_build_lib.RunCommandError:
436 # Clean up existing interfaces, if any.
437 cmd_cleanup = ('ip', 'link', 'del', veth_host)
438 try:
439 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
440 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700441 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800442 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500443
444 # Signal the child that the net ns/proxy is fully configured now.
445 ns_setup_lock.Post()
446 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800447
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400448 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800449
450
Mike Frysingera78a56e2012-11-20 06:02:30 -0500451def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700452 """Re-execute cros_sdk as root.
453
454 Also unshare the mount namespace so as to ensure that processes outside
455 the chroot can't mess with our mounts.
456 """
457 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500458 cmd = _SudoCommand() + ['--'] + argv
459 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500460 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400461 # We must set up the cgroups mounts before we enter our own namespace.
462 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800463 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500464 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700465
466
Mike Frysinger34db8692013-11-11 14:54:08 -0500467def _CreateParser(sdk_latest_version, bootstrap_latest_version):
468 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400469 usage = ('usage: %(prog)s [options] '
470 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
471 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
472 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700473
Mike Frysinger34db8692013-11-11 14:54:08 -0500474 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500475 default_chroot = os.path.join(constants.SOURCE_ROOT,
476 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400477 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700478 '--chroot', dest='chroot', default=default_chroot, type='path',
479 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800480
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400481 parser.add_argument('--chrome_root', type='path',
482 help='Mount this chrome root into the SDK chroot')
483 parser.add_argument('--chrome_root_mount', type='path',
484 help='Mount chrome into this path inside SDK chroot')
485 parser.add_argument('--nousepkg', action='store_true', default=False,
486 help='Do not use binary packages when creating a chroot.')
487 parser.add_argument('-u', '--url', dest='sdk_url',
488 help='Use sdk tarball located at this url. Use file:// '
489 'for local files.')
490 parser.add_argument('--sdk-version',
491 help=('Use this sdk version. For prebuilt, current is %r'
492 ', for bootstrapping it is %r.'
493 % (sdk_latest_version, bootstrap_latest_version)))
494 parser.add_argument('--workspace',
495 help='Workspace directory to mount into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400496 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500497
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700498 # SDK overlay tarball options (mutually exclusive).
499 group = parser.add_mutually_exclusive_group()
500 group.add_argument('--toolchains',
501 help=('Comma-separated list of toolchains we expect to be '
502 'using on the chroot. Used for downloading a '
503 'corresponding SDK toolchains group (if one is '
504 'found), which may speed up chroot initialization '
505 'when building for the first time. Otherwise this '
506 'has no effect and will not restrict the chroot in '
507 'any way. Ignored if using --bootstrap.'))
508 group.add_argument('--board',
509 help=('The board we intend to be building in the chroot. '
510 'Used for deriving the list of required toolchains '
511 '(see --toolchains).'))
512
Mike Frysinger34db8692013-11-11 14:54:08 -0500513 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400514 group = parser.add_argument_group('Commands')
515 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500516 '--enter', action='store_true', default=False,
517 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400518 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500519 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500520 help='Create the chroot only if it does not already exist. '
521 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400522 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500523 '--bootstrap', action='store_true', default=False,
524 help='Build everything from scratch, including the sdk. '
525 'Use this only if you need to validate a change '
526 'that affects SDK creation itself (toolchain and '
527 'build are typically the only folk who need this). '
528 'Note this will quite heavily slow down the build. '
529 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400530 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500531 '-r', '--replace', action='store_true', default=False,
532 help='Replace an existing SDK chroot. Basically an alias '
533 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400534 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500535 '--delete', action='store_true', default=False,
536 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400537 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500538 '--download', action='store_true', default=False,
539 help='Download the sdk.')
540 commands = group
541
Mike Frysinger80dfce92014-04-21 10:58:53 -0400542 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400543 group = parser.add_argument_group('Namespaces')
544 group.add_argument('--proxy-sim', action='store_true', default=False,
545 help='Simulate a restrictive network requiring an outbound'
546 ' proxy.')
547 group.add_argument('--no-ns-pid', dest='ns_pid',
548 default=True, action='store_false',
549 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400550
Mike Frysinger34db8692013-11-11 14:54:08 -0500551 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400552 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500553 'Internal Chromium OS Build Team Options',
554 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400555 group.add_argument('--buildbot-log-version', default=False,
556 action='store_true',
557 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500558
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400559 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500560
561
562def main(argv):
563 conf = cros_build_lib.LoadKeyValueFile(
564 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
565 ignore_missing=True)
566 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
567 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
568 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400569 options = parser.parse_args(argv)
570 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800571
572 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500573 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800574
Brian Harring1790ac42012-09-23 08:53:33 -0700575 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700576 if host != 'x86_64':
577 parser.error(
578 "cros_sdk is currently only supported on x86_64; you're running"
579 " %s. Please find a x86_64 machine." % (host,))
580
Josh Triplett472a4182013-03-08 11:48:57 -0800581 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
582 if options.proxy_sim:
583 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800584
David James471532c2013-01-21 10:23:31 -0800585 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400586 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400587 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400588 else:
589 first_pid = None
David James471532c2013-01-21 10:23:31 -0800590
Brian Harring218e13c2012-10-10 16:21:26 -0700591 # Expand out the aliases...
592 if options.replace:
593 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800594
Brian Harring218e13c2012-10-10 16:21:26 -0700595 if options.bootstrap:
596 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800597
Brian Harring218e13c2012-10-10 16:21:26 -0700598 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400599 # pylint: disable=protected-access
600 # This _group_actions access sucks, but upstream decided to not include an
601 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700602 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400603 for x in commands._group_actions)
604 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700605 options.enter |= bool(chroot_command)
606
607 if options.enter and options.delete and not options.create:
608 parser.error("Trying to enter the chroot when --delete "
609 "was specified makes no sense.")
610
611 # Finally, discern if we need to create the chroot.
612 chroot_exists = os.path.exists(options.chroot)
613 if options.create or options.enter:
614 # Only create if it's being wiped, or if it doesn't exist.
615 if not options.delete and chroot_exists:
616 options.create = False
617 else:
618 options.download = True
619
620 # Finally, flip create if necessary.
621 if options.enter:
622 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800623
Brian Harringb938c782012-02-29 15:14:38 -0800624 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700625 sdk_version = (bootstrap_latest_version if options.bootstrap
626 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800627 else:
628 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500629 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700630 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800631
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700632 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700633 if options.sdk_url:
634 urls = [options.sdk_url]
635 elif options.bootstrap:
636 urls = GetStage3Urls(sdk_version)
637 else:
638 urls = GetArchStageTarballs(sdk_version)
639
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700640 # Get URLs for the toolchains overlay, if one is to be used.
641 toolchains_overlay_urls = None
642 if not options.bootstrap:
643 toolchains = None
644 if options.toolchains:
645 toolchains = options.toolchains.split(',')
646 elif options.board:
647 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
648
649 if toolchains:
650 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
651 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700652
Brian Harringb6cf9142012-09-01 20:43:17 -0700653 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700654 lock_path = os.path.join(
655 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400656 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700657 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700658 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700659
Josh Triplett472a4182013-03-08 11:48:57 -0800660 if options.proxy_sim:
661 _ProxySimSetup(options)
662
David James56e6c2c2012-10-24 23:54:41 -0700663 if options.delete and os.path.exists(options.chroot):
664 lock.write_lock()
665 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800666
David James56e6c2c2012-10-24 23:54:41 -0700667 sdk_cache = os.path.join(options.cache_dir, 'sdks')
668 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700669 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700670
David James56e6c2c2012-10-24 23:54:41 -0700671 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500672 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700673 if not os.path.exists(src):
674 osutils.SafeMakedirs(target)
675 continue
676 lock.write_lock(
677 "Upgrade to %r needed but chroot is locked; please exit "
678 "all instances so this upgrade can finish." % src)
679 if not os.path.exists(src):
680 # Note that while waiting for the write lock, src may've vanished;
681 # it's a rare race during the upgrade process that's a byproduct
682 # of us avoiding taking a write lock to do the src check. If we
683 # took a write lock for that check, it would effectively limit
684 # all cros_sdk for a chroot to a single instance.
685 osutils.SafeMakedirs(target)
686 elif not os.path.exists(target):
687 # Upgrade occurred, but a reversion, or something whacky
688 # occurred writing to the old location. Wipe and continue.
689 os.rename(src, target)
690 else:
691 # Upgrade occurred once already, but either a reversion or
692 # some before/after separate cros_sdk usage is at play.
693 # Wipe and continue.
694 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700695
David James56e6c2c2012-10-24 23:54:41 -0700696 if options.download:
697 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700698 sdk_tarball = FetchRemoteTarballs(
699 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
700 if toolchains_overlay_urls:
701 toolchains_overlay_tarball = FetchRemoteTarballs(
702 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
703 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700704
David James56e6c2c2012-10-24 23:54:41 -0700705 if options.create:
706 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700707 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700708 options.cache_dir,
David Pursell3528e8e2015-07-07 11:22:15 -0700709 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700710
David James56e6c2c2012-10-24 23:54:41 -0700711 if options.enter:
712 lock.read_lock()
713 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700714 options.chrome_root_mount, options.workspace,
715 chroot_command)