blob: b47475dd90142e6281496557d63c78efcb9128e0 [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
344 # Set up pipes from parent to child and vice versa.
345 # The child writes a byte to the parent after calling unshare, so that the
346 # parent can then assign the guest end of the veth interface to the child's
347 # new network namespace. The parent then writes a byte to the child after
348 # assigning the guest interface, so that the child can then configure that
349 # interface. In both cases, if we get back an EOF when reading from the
350 # pipe, we assume the other end exited with an error message, so just exit.
351 parent_readfd, child_writefd = os.pipe()
352 child_readfd, parent_writefd = os.pipe()
353 SUCCESS_FLAG = '+'
354
355 pid = os.fork()
356 if not pid:
357 os.close(parent_readfd)
358 os.close(parent_writefd)
359
360 namespaces.Unshare(namespaces.CLONE_NEWNET)
361 os.write(child_writefd, SUCCESS_FLAG)
362 os.close(child_writefd)
363 if os.read(child_readfd, 1) != SUCCESS_FLAG:
364 # Parent failed; it will have already have outputted an error message.
365 sys.exit(1)
366 os.close(child_readfd)
367
368 # Set up child side of the network.
369 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500370 ('ip', 'link', 'set', 'up', 'lo'),
371 ('ip', 'address', 'add',
372 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
373 'dev', veth_guest),
374 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800375 )
376 try:
377 for cmd in commands:
378 cros_build_lib.RunCommand(cmd, print_cmd=False)
379 except cros_build_lib.RunCommandError:
380 raise SystemExit('Running %r failed!' % (cmd,))
381
382 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
383 for proto in ('http', 'https', 'ftp'):
384 os.environ[proto + '_proxy'] = proxy_url
385 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
386 os.environ.pop(v, None)
387 return
388
389 os.close(child_readfd)
390 os.close(child_writefd)
391
392 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
393 # Child failed; it will have already have outputted an error message.
394 sys.exit(1)
395 os.close(parent_readfd)
396
397 # Set up parent side of the network.
398 uid = int(os.environ.get('SUDO_UID', '0'))
399 gid = int(os.environ.get('SUDO_GID', '0'))
400 if uid == 0 or gid == 0:
401 for username in PROXY_APACHE_FALLBACK_USERS:
402 try:
403 pwnam = pwd.getpwnam(username)
404 uid, gid = pwnam.pw_uid, pwnam.pw_gid
405 break
406 except KeyError:
407 continue
408 if uid == 0 or gid == 0:
409 raise SystemExit('Could not find a non-root user to run Apache as')
410
411 chroot_parent, chroot_base = os.path.split(options.chroot)
412 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
413 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
414
415 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500416 'User #%u' % uid,
417 'Group #%u' % gid,
418 'PidFile %s' % pid_file,
419 'ErrorLog %s' % log_file,
420 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
421 'ServerName %s' % PROXY_HOST_IP,
422 'ProxyRequests On',
423 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800424 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500425 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
426 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800427 ]
428 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500429 ('ip', 'link', 'add', 'name', veth_host,
430 'type', 'veth', 'peer', 'name', veth_guest),
431 ('ip', 'address', 'add',
432 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
433 'dev', veth_host),
434 ('ip', 'link', 'set', veth_host, 'up'),
435 ([apache_bin, '-f', '/dev/null'] +
436 [arg for d in apache_directives for arg in ('-C', d)]),
437 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800438 )
439 cmd = None # Make cros lint happy.
440 try:
441 for cmd in commands:
442 cros_build_lib.RunCommand(cmd, print_cmd=False)
443 except cros_build_lib.RunCommandError:
444 # Clean up existing interfaces, if any.
445 cmd_cleanup = ('ip', 'link', 'del', veth_host)
446 try:
447 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
448 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700449 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800450 raise SystemExit('Running %r failed!' % (cmd,))
451 os.write(parent_writefd, SUCCESS_FLAG)
452 os.close(parent_writefd)
453
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400454 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800455
456
Mike Frysingera78a56e2012-11-20 06:02:30 -0500457def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700458 """Re-execute cros_sdk as root.
459
460 Also unshare the mount namespace so as to ensure that processes outside
461 the chroot can't mess with our mounts.
462 """
463 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500464 cmd = _SudoCommand() + ['--'] + argv
465 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500466 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400467 # We must set up the cgroups mounts before we enter our own namespace.
468 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800469 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500470 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700471
472
Mike Frysinger34db8692013-11-11 14:54:08 -0500473def _CreateParser(sdk_latest_version, bootstrap_latest_version):
474 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400475 usage = ('usage: %(prog)s [options] '
476 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
477 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
478 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700479
Mike Frysinger34db8692013-11-11 14:54:08 -0500480 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500481 default_chroot = os.path.join(constants.SOURCE_ROOT,
482 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400483 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700484 '--chroot', dest='chroot', default=default_chroot, type='path',
485 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800486
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400487 parser.add_argument('--chrome_root', type='path',
488 help='Mount this chrome root into the SDK chroot')
489 parser.add_argument('--chrome_root_mount', type='path',
490 help='Mount chrome into this path inside SDK chroot')
491 parser.add_argument('--nousepkg', action='store_true', default=False,
492 help='Do not use binary packages when creating a chroot.')
493 parser.add_argument('-u', '--url', dest='sdk_url',
494 help='Use sdk tarball located at this url. Use file:// '
495 'for local files.')
496 parser.add_argument('--sdk-version',
497 help=('Use this sdk version. For prebuilt, current is %r'
498 ', for bootstrapping it is %r.'
499 % (sdk_latest_version, bootstrap_latest_version)))
500 parser.add_argument('--workspace',
501 help='Workspace directory to mount into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400502 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500503
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700504 # SDK overlay tarball options (mutually exclusive).
505 group = parser.add_mutually_exclusive_group()
506 group.add_argument('--toolchains',
507 help=('Comma-separated list of toolchains we expect to be '
508 'using on the chroot. Used for downloading a '
509 'corresponding SDK toolchains group (if one is '
510 'found), which may speed up chroot initialization '
511 'when building for the first time. Otherwise this '
512 'has no effect and will not restrict the chroot in '
513 'any way. Ignored if using --bootstrap.'))
514 group.add_argument('--board',
515 help=('The board we intend to be building in the chroot. '
516 'Used for deriving the list of required toolchains '
517 '(see --toolchains).'))
518
Mike Frysinger34db8692013-11-11 14:54:08 -0500519 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400520 group = parser.add_argument_group('Commands')
521 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500522 '--enter', action='store_true', default=False,
523 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400524 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500525 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500526 help='Create the chroot only if it does not already exist. '
527 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400528 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500529 '--bootstrap', action='store_true', default=False,
530 help='Build everything from scratch, including the sdk. '
531 'Use this only if you need to validate a change '
532 'that affects SDK creation itself (toolchain and '
533 'build are typically the only folk who need this). '
534 'Note this will quite heavily slow down the build. '
535 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400536 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500537 '-r', '--replace', action='store_true', default=False,
538 help='Replace an existing SDK chroot. Basically an alias '
539 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400540 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500541 '--delete', action='store_true', default=False,
542 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400543 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500544 '--download', action='store_true', default=False,
545 help='Download the sdk.')
546 commands = group
547
Mike Frysinger80dfce92014-04-21 10:58:53 -0400548 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400549 group = parser.add_argument_group('Namespaces')
550 group.add_argument('--proxy-sim', action='store_true', default=False,
551 help='Simulate a restrictive network requiring an outbound'
552 ' proxy.')
553 group.add_argument('--no-ns-pid', dest='ns_pid',
554 default=True, action='store_false',
555 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400556
Mike Frysinger34db8692013-11-11 14:54:08 -0500557 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400558 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500559 'Internal Chromium OS Build Team Options',
560 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400561 group.add_argument('--buildbot-log-version', default=False,
562 action='store_true',
563 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500564
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400565 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500566
567
568def main(argv):
569 conf = cros_build_lib.LoadKeyValueFile(
570 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
571 ignore_missing=True)
572 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
573 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
574 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400575 options = parser.parse_args(argv)
576 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800577
578 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500579 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800580
Brian Harring1790ac42012-09-23 08:53:33 -0700581 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700582 if host != 'x86_64':
583 parser.error(
584 "cros_sdk is currently only supported on x86_64; you're running"
585 " %s. Please find a x86_64 machine." % (host,))
586
Josh Triplett472a4182013-03-08 11:48:57 -0800587 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
588 if options.proxy_sim:
589 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800590
David James471532c2013-01-21 10:23:31 -0800591 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400592 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400593 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400594 else:
595 first_pid = None
David James471532c2013-01-21 10:23:31 -0800596
Brian Harring218e13c2012-10-10 16:21:26 -0700597 # Expand out the aliases...
598 if options.replace:
599 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800600
Brian Harring218e13c2012-10-10 16:21:26 -0700601 if options.bootstrap:
602 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800603
Brian Harring218e13c2012-10-10 16:21:26 -0700604 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400605 # pylint: disable=protected-access
606 # This _group_actions access sucks, but upstream decided to not include an
607 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700608 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400609 for x in commands._group_actions)
610 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700611 options.enter |= bool(chroot_command)
612
613 if options.enter and options.delete and not options.create:
614 parser.error("Trying to enter the chroot when --delete "
615 "was specified makes no sense.")
616
617 # Finally, discern if we need to create the chroot.
618 chroot_exists = os.path.exists(options.chroot)
619 if options.create or options.enter:
620 # Only create if it's being wiped, or if it doesn't exist.
621 if not options.delete and chroot_exists:
622 options.create = False
623 else:
624 options.download = True
625
626 # Finally, flip create if necessary.
627 if options.enter:
628 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800629
Brian Harringb938c782012-02-29 15:14:38 -0800630 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700631 sdk_version = (bootstrap_latest_version if options.bootstrap
632 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800633 else:
634 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500635 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700636 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800637
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700638 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700639 if options.sdk_url:
640 urls = [options.sdk_url]
641 elif options.bootstrap:
642 urls = GetStage3Urls(sdk_version)
643 else:
644 urls = GetArchStageTarballs(sdk_version)
645
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700646 # Get URLs for the toolchains overlay, if one is to be used.
647 toolchains_overlay_urls = None
648 if not options.bootstrap:
649 toolchains = None
650 if options.toolchains:
651 toolchains = options.toolchains.split(',')
652 elif options.board:
653 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
654
655 if toolchains:
656 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
657 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700658
Brian Harringb6cf9142012-09-01 20:43:17 -0700659 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700660 lock_path = os.path.join(
661 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400662 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700663 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700664 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700665
Josh Triplett472a4182013-03-08 11:48:57 -0800666 if options.proxy_sim:
667 _ProxySimSetup(options)
668
David James56e6c2c2012-10-24 23:54:41 -0700669 if options.delete and os.path.exists(options.chroot):
670 lock.write_lock()
671 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800672
David James56e6c2c2012-10-24 23:54:41 -0700673 sdk_cache = os.path.join(options.cache_dir, 'sdks')
674 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700675 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700676
David James56e6c2c2012-10-24 23:54:41 -0700677 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500678 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700679 if not os.path.exists(src):
680 osutils.SafeMakedirs(target)
681 continue
682 lock.write_lock(
683 "Upgrade to %r needed but chroot is locked; please exit "
684 "all instances so this upgrade can finish." % src)
685 if not os.path.exists(src):
686 # Note that while waiting for the write lock, src may've vanished;
687 # it's a rare race during the upgrade process that's a byproduct
688 # of us avoiding taking a write lock to do the src check. If we
689 # took a write lock for that check, it would effectively limit
690 # all cros_sdk for a chroot to a single instance.
691 osutils.SafeMakedirs(target)
692 elif not os.path.exists(target):
693 # Upgrade occurred, but a reversion, or something whacky
694 # occurred writing to the old location. Wipe and continue.
695 os.rename(src, target)
696 else:
697 # Upgrade occurred once already, but either a reversion or
698 # some before/after separate cros_sdk usage is at play.
699 # Wipe and continue.
700 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700701
David James56e6c2c2012-10-24 23:54:41 -0700702 if options.download:
703 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700704 sdk_tarball = FetchRemoteTarballs(
705 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
706 if toolchains_overlay_urls:
707 toolchains_overlay_tarball = FetchRemoteTarballs(
708 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
709 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700710
David James56e6c2c2012-10-24 23:54:41 -0700711 if options.create:
712 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700713 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700714 options.cache_dir,
David Pursell3528e8e2015-07-07 11:22:15 -0700715 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700716
David James56e6c2c2012-10-24 23:54:41 -0700717 if options.enter:
718 lock.read_lock()
719 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700720 options.chrome_root_mount, options.workspace,
721 chroot_command)