blob: 4eb18b7ea14e7744a56bb7707be142de6b5a6801 [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 Frysinger80dfce92014-04-21 10:58:53 -04005"""This script fetches and prepares an SDK chroot."""
Brian Harringb938c782012-02-29 15:14:38 -08006
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Josh Triplett472a4182013-03-08 11:48:57 -08009import glob
Brian Harringb938c782012-02-29 15:14:38 -080010import os
Josh Triplett472a4182013-03-08 11:48:57 -080011import pwd
David James56e6c2c2012-10-24 23:54:41 -070012import sys
Brian Harringb938c782012-02-29 15:14:38 -080013import urlparse
14
Don Garrett88b8d782014-05-13 17:30:55 -070015from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080016from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070017from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080018from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070019from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080020from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080021from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070022from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040023from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080024from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050025from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080026
27cros_build_lib.STRICT_SUDO = True
28
29
Zdenek Behanaa52cea2012-05-30 01:31:11 +020030COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020031
Brian Harringb938c782012-02-29 15:14:38 -080032# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050033MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
34 'src/scripts/sdk_lib/make_chroot.sh')]
35ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
36 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080037
Josh Triplett472a4182013-03-08 11:48:57 -080038# Proxy simulator configuration.
39PROXY_HOST_IP = '192.168.240.1'
40PROXY_PORT = 8080
41PROXY_GUEST_IP = '192.168.240.2'
42PROXY_NETMASK = 30
43PROXY_VETH_PREFIX = 'veth'
44PROXY_CONNECT_PORTS = (80, 443, 9418)
45PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
46PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
47PROXY_APACHE_FALLBACK_PATH = ':'.join(
48 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
49)
50PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
51
Josh Triplett9a495f62013-03-15 18:06:55 -070052# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080053NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080054
Josh Triplett472a4182013-03-08 11:48:57 -080055# Tools needed for --proxy-sim only.
56PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080057
Mike Frysingercc838832014-05-24 13:10:30 -040058
Brian Harring1790ac42012-09-23 08:53:33 -070059def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080060 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070061 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050062 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
63 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070064 for compressor in COMPRESSION_PREFERENCE]
65
66
67def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050068 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070069 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080070
71
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070072def GetBoardOverlayUrls(version, board):
73 """Returns the URL(s) for the board-specific toolchain overlay.
74
75 Args:
76 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
77 month components to point to a subdirectory on the SDK bucket where
78 board overlays are stored (.../2015/05/ in this case).
79 board: The board name.
80
81 Returns:
82 List of alternative download URLs for the board's SDK overlay tarball.
83 """
84 suburl_template = os.path.join(
85 *(version.split('.')[:2] +
86 ['cros-sdk-overlay-%s-%s.tar.%%s' % (board, version)]))
87 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
88 for ext in COMPRESSION_PREFERENCE]
89
90
91def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -050092 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020093
94 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050095 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020096 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070097 desc: A string describing what's the thing we're downloading (for logging).
98 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020099
100 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700101 Full path to the downloaded file, or None if |allow_none| and no URL worked.
102
103 Raises:
104 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200105 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200106
Brian Harring1790ac42012-09-23 08:53:33 -0700107 # Note we track content length ourselves since certain versions of curl
108 # fail if asked to resume a complete file.
109 # pylint: disable=C0301,W0631
110 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700111 logging.notice('Downloading %s...', desc)
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700113 # http://www.logilab.org/ticket/8766
114 # pylint: disable=E1101
115 parsed = urlparse.urlparse(url)
116 tarball_name = os.path.basename(parsed.path)
117 if parsed.scheme in ('', 'file'):
118 if os.path.exists(parsed.path):
119 return parsed.path
120 continue
121 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700122 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800123 result = retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400124 ['-I', url], fail=False, redirect_stdout=True, redirect_stderr=True,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500125 print_cmd=False)
Brian Harring1790ac42012-09-23 08:53:33 -0700126 successful = False
127 for header in result.output.splitlines():
128 # We must walk the output to find the string '200 OK' for use cases where
129 # a proxy is involved and may have pushed down the actual header.
130 if header.find('200 OK') != -1:
131 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500132 elif header.lower().startswith('content-length:'):
133 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700134 if successful:
135 break
136 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200137 break
138 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700139 if allow_none:
140 return None
141 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200142
Brian Harringae0a5322012-09-15 01:46:51 -0700143 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700144 current_size = 0
145 if os.path.exists(tarball_dest):
146 current_size = os.path.getsize(tarball_dest)
147 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700148 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700149 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100150
Brian Harring1790ac42012-09-23 08:53:33 -0700151 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800152 retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400153 ['-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Brian Harring1790ac42012-09-23 08:53:33 -0700154 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800155
Brian Harring1790ac42012-09-23 08:53:33 -0700156 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700157 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
158 # because we might have partial overlap between known prefixes.
159 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
160 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
161 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700162 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700163 if (filename == tarball_name or
164 any([(filename.startswith(p) and
165 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
166 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700167 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700168 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700169 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200170
Brian Harringb938c782012-02-29 15:14:38 -0800171 return tarball_dest
172
173
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700174def CreateChroot(chroot_path, sdk_tarball, board_overlay_tarball, cache_dir,
175 nousepkg=False, workspace=None):
Brian Harringb938c782012-02-29 15:14:38 -0800176 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800177
Brian Harring1790ac42012-09-23 08:53:33 -0700178 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700179 '--chroot', chroot_path,
180 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700181
182 if board_overlay_tarball:
183 cmd.extend(['--board_overlay_path', board_overlay_tarball])
184
Mike Frysinger2de7f042012-07-10 04:45:03 -0400185 if nousepkg:
186 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800187
David Purselld99f6222015-05-04 16:52:59 -0700188 if workspace:
189 cmd.extend(['--workspace_root', workspace])
190
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700191 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800192 try:
193 cros_build_lib.RunCommand(cmd, print_cmd=False)
194 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700195 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800196
197
198def DeleteChroot(chroot_path):
199 """Deletes an existing chroot"""
200 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
201 '--delete']
202 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700203 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800204 cros_build_lib.RunCommand(cmd, print_cmd=False)
205 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700206 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800207
208
Brian Harringae0a5322012-09-15 01:46:51 -0700209def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700210 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800211 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400212 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
213 # The os.ST_NOSUID constant wasn't added until python-3.2.
214 if st.f_flag & 0x2:
215 cros_build_lib.Die('chroot cannot be in a nosuid mount')
216
Brian Harringae0a5322012-09-15 01:46:51 -0700217 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800218 if chrome_root:
219 cmd.extend(['--chrome_root', chrome_root])
220 if chrome_root_mount:
221 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700222 if workspace:
223 cmd.extend(['--workspace_root', workspace])
224
Brian Harringb938c782012-02-29 15:14:38 -0800225 if len(additional_args) > 0:
226 cmd.append('--')
227 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700228
Ralph Nathan549d3502015-03-26 17:38:42 -0700229 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
230 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700231 # If we were in interactive mode, ignore the exit code; it'll be whatever
232 # they last ran w/in the chroot and won't matter to us one way or another.
233 # Note this does allow chroot entrance to fail and be ignored during
234 # interactive; this is however a rare case and the user will immediately
235 # see it (nor will they be checking the exit code manually).
236 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700237 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800238
239
David James56e6c2c2012-10-24 23:54:41 -0700240def _SudoCommand():
241 """Get the 'sudo' command, along with all needed environment variables."""
242
David James5a73b4d2013-03-07 10:23:40 -0800243 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
244 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700245 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800246 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700247 value = os.environ.get(key)
248 if value is not None:
249 cmd += ['%s=%s' % (key, value)]
250
251 # Pass in the path to the depot_tools so that users can access them from
252 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400253 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500254
David James56e6c2c2012-10-24 23:54:41 -0700255 return cmd
256
257
Josh Triplett472a4182013-03-08 11:48:57 -0800258def _ReportMissing(missing):
259 """Report missing utilities, then exit.
260
261 Args:
262 missing: List of missing utilities, as returned by
263 osutils.FindMissingBinaries. If non-empty, will not return.
264 """
265
266 if missing:
267 raise SystemExit(
268 'The tool(s) %s were not found.\n'
269 'Please install the appropriate package in your host.\n'
270 'Example(ubuntu):\n'
271 ' sudo apt-get install <packagename>'
272 % ', '.join(missing))
273
274
275def _ProxySimSetup(options):
276 """Set up proxy simulator, and return only in the child environment.
277
278 TODO: Ideally, this should support multiple concurrent invocations of
279 cros_sdk --proxy-sim; currently, such invocations will conflict with each
280 other due to the veth device names and IP addresses. Either this code would
281 need to generate fresh, unused names for all of these before forking, or it
282 would need to support multiple concurrent cros_sdk invocations sharing one
283 proxy and allowing it to exit when unused (without counting on any local
284 service-management infrastructure on the host).
285 """
286
287 may_need_mpm = False
288 apache_bin = osutils.Which('apache2')
289 if apache_bin is None:
290 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
291 if apache_bin is None:
292 _ReportMissing(('apache2',))
293 else:
294 may_need_mpm = True
295
296 # Module names and .so names included for ease of grepping.
297 apache_modules = [('proxy_module', 'mod_proxy.so'),
298 ('proxy_connect_module', 'mod_proxy_connect.so'),
299 ('proxy_http_module', 'mod_proxy_http.so'),
300 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
301
302 # Find the apache module directory, and make sure it has the modules we need.
303 module_dirs = {}
304 for g in PROXY_APACHE_MODULE_GLOBS:
305 for mod, so in apache_modules:
306 for f in glob.glob(os.path.join(g, so)):
307 module_dirs.setdefault(os.path.dirname(f), []).append(so)
308 for apache_module_path, modules_found in module_dirs.iteritems():
309 if len(modules_found) == len(apache_modules):
310 break
311 else:
312 # Appease cros lint, which doesn't understand that this else block will not
313 # fall through to the subsequent code which relies on apache_module_path.
314 apache_module_path = None
315 raise SystemExit(
316 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500317 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800318
319 def check_add_module(name):
320 so = 'mod_%s.so' % name
321 if os.access(os.path.join(apache_module_path, so), os.F_OK):
322 mod = '%s_module' % name
323 apache_modules.append((mod, so))
324 return True
325 return False
326
327 check_add_module('authz_core')
328 if may_need_mpm:
329 for mpm in PROXY_APACHE_MPMS:
330 if check_add_module('mpm_%s' % mpm):
331 break
332
333 veth_host = '%s-host' % PROXY_VETH_PREFIX
334 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
335
336 # Set up pipes from parent to child and vice versa.
337 # The child writes a byte to the parent after calling unshare, so that the
338 # parent can then assign the guest end of the veth interface to the child's
339 # new network namespace. The parent then writes a byte to the child after
340 # assigning the guest interface, so that the child can then configure that
341 # interface. In both cases, if we get back an EOF when reading from the
342 # pipe, we assume the other end exited with an error message, so just exit.
343 parent_readfd, child_writefd = os.pipe()
344 child_readfd, parent_writefd = os.pipe()
345 SUCCESS_FLAG = '+'
346
347 pid = os.fork()
348 if not pid:
349 os.close(parent_readfd)
350 os.close(parent_writefd)
351
352 namespaces.Unshare(namespaces.CLONE_NEWNET)
353 os.write(child_writefd, SUCCESS_FLAG)
354 os.close(child_writefd)
355 if os.read(child_readfd, 1) != SUCCESS_FLAG:
356 # Parent failed; it will have already have outputted an error message.
357 sys.exit(1)
358 os.close(child_readfd)
359
360 # Set up child side of the network.
361 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500362 ('ip', 'link', 'set', 'up', 'lo'),
363 ('ip', 'address', 'add',
364 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
365 'dev', veth_guest),
366 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800367 )
368 try:
369 for cmd in commands:
370 cros_build_lib.RunCommand(cmd, print_cmd=False)
371 except cros_build_lib.RunCommandError:
372 raise SystemExit('Running %r failed!' % (cmd,))
373
374 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
375 for proto in ('http', 'https', 'ftp'):
376 os.environ[proto + '_proxy'] = proxy_url
377 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
378 os.environ.pop(v, None)
379 return
380
381 os.close(child_readfd)
382 os.close(child_writefd)
383
384 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
385 # Child failed; it will have already have outputted an error message.
386 sys.exit(1)
387 os.close(parent_readfd)
388
389 # Set up parent side of the network.
390 uid = int(os.environ.get('SUDO_UID', '0'))
391 gid = int(os.environ.get('SUDO_GID', '0'))
392 if uid == 0 or gid == 0:
393 for username in PROXY_APACHE_FALLBACK_USERS:
394 try:
395 pwnam = pwd.getpwnam(username)
396 uid, gid = pwnam.pw_uid, pwnam.pw_gid
397 break
398 except KeyError:
399 continue
400 if uid == 0 or gid == 0:
401 raise SystemExit('Could not find a non-root user to run Apache as')
402
403 chroot_parent, chroot_base = os.path.split(options.chroot)
404 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
405 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
406
407 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,))
443 os.write(parent_writefd, SUCCESS_FLAG)
444 os.close(parent_writefd)
445
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400446 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800447
448
Mike Frysingera78a56e2012-11-20 06:02:30 -0500449def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700450 """Re-execute cros_sdk as root.
451
452 Also unshare the mount namespace so as to ensure that processes outside
453 the chroot can't mess with our mounts.
454 """
455 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500456 cmd = _SudoCommand() + ['--'] + argv
457 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500458 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400459 # We must set up the cgroups mounts before we enter our own namespace.
460 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800461 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500462 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700463
464
Mike Frysinger34db8692013-11-11 14:54:08 -0500465def _CreateParser(sdk_latest_version, bootstrap_latest_version):
466 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700467 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800468
Brian Harring218e13c2012-10-10 16:21:26 -0700469This script is used for manipulating local chroot environments; creating,
470deleting, downloading, etc. If given --enter (or no args), it defaults
471to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800472
Brian Harring218e13c2012-10-10 16:21:26 -0700473If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700474
Brian Harring218e13c2012-10-10 16:21:26 -0700475 parser = commandline.OptionParser(usage=usage, caching=True)
476
Mike Frysinger34db8692013-11-11 14:54:08 -0500477 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500478 default_chroot = os.path.join(constants.SOURCE_ROOT,
479 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700480 parser.add_option(
481 '--chroot', dest='chroot', default=default_chroot, type='path',
482 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800483
Brian Harring218e13c2012-10-10 16:21:26 -0700484 parser.add_option('--chrome_root', default=None, type='path',
485 help='Mount this chrome root into the SDK chroot')
486 parser.add_option('--chrome_root_mount', default=None, type='path',
487 help='Mount chrome into this path inside SDK chroot')
488 parser.add_option('--nousepkg', action='store_true', default=False,
489 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800490 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700491 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800492 help=('''Use sdk tarball located at this url.
493 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700494 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500495 help=('Use this sdk version. For prebuilt, current is %r'
496 ', for bootstrapping it is %r.'
497 % (sdk_latest_version, bootstrap_latest_version)))
Don Garrett230d1b22015-03-09 16:21:19 -0700498 parser.add_option('--workspace', default=None,
499 help='Workspace directory to mount into the chroot.')
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700500 parser.add_option('--board', default=None,
501 help='The board we intend to be building in the chroot. '
502 'Used for downloading an SDK board overlay (if one is '
503 'found), which may speed up chroot initialization when '
504 'building for the first time. Otherwise this has no effect '
505 'and will not restrict the chroot in any way. Ignored if '
506 'using --bootstrap.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500507
508 # Commands.
509 group = parser.add_option_group('Commands')
510 group.add_option(
511 '--enter', action='store_true', default=False,
512 help='Enter the SDK chroot. Implies --create.')
513 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500514 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500515 help='Create the chroot only if it does not already exist. '
516 'Implies --download.')
517 group.add_option(
518 '--bootstrap', action='store_true', default=False,
519 help='Build everything from scratch, including the sdk. '
520 'Use this only if you need to validate a change '
521 'that affects SDK creation itself (toolchain and '
522 'build are typically the only folk who need this). '
523 'Note this will quite heavily slow down the build. '
524 'This option implies --create --nousepkg.')
525 group.add_option(
526 '-r', '--replace', action='store_true', default=False,
527 help='Replace an existing SDK chroot. Basically an alias '
528 'for --delete --create.')
529 group.add_option(
530 '--delete', action='store_true', default=False,
531 help='Delete the current SDK chroot if it exists.')
532 group.add_option(
533 '--download', action='store_true', default=False,
534 help='Download the sdk.')
535 commands = group
536
Mike Frysinger80dfce92014-04-21 10:58:53 -0400537 # Namespace options.
538 group = parser.add_option_group('Namespaces')
539 group.add_option('--proxy-sim', action='store_true', default=False,
540 help='Simulate a restrictive network requiring an outbound'
541 ' proxy.')
542 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger41f28032014-09-29 16:53:41 -0500543 default=True, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400544 help='Do not create a new PID namespace.')
545
Mike Frysinger34db8692013-11-11 14:54:08 -0500546 # Internal options.
547 group = parser.add_option_group(
548 'Internal Chromium OS Build Team Options',
549 'Caution: these are for meant for the Chromium OS build team only')
550 group.add_option('--buildbot-log-version', default=False, action='store_true',
551 help='Log SDK version for buildbot consumption')
552
553 return (parser, commands)
554
555
556def main(argv):
557 conf = cros_build_lib.LoadKeyValueFile(
558 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
559 ignore_missing=True)
560 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
561 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
562 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700563 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800564
565 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500566 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800567
Brian Harring1790ac42012-09-23 08:53:33 -0700568 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700569 if host != 'x86_64':
570 parser.error(
571 "cros_sdk is currently only supported on x86_64; you're running"
572 " %s. Please find a x86_64 machine." % (host,))
573
Josh Triplett472a4182013-03-08 11:48:57 -0800574 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
575 if options.proxy_sim:
576 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800577
David James471532c2013-01-21 10:23:31 -0800578 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400579 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400580 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400581 else:
582 first_pid = None
David James471532c2013-01-21 10:23:31 -0800583
Brian Harring218e13c2012-10-10 16:21:26 -0700584 # Expand out the aliases...
585 if options.replace:
586 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800587
Brian Harring218e13c2012-10-10 16:21:26 -0700588 if options.bootstrap:
589 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800590
Brian Harring218e13c2012-10-10 16:21:26 -0700591 # If a command is not given, default to enter.
592 options.enter |= not any(getattr(options, x.dest)
593 for x in commands.option_list)
594 options.enter |= bool(chroot_command)
595
596 if options.enter and options.delete and not options.create:
597 parser.error("Trying to enter the chroot when --delete "
598 "was specified makes no sense.")
599
600 # Finally, discern if we need to create the chroot.
601 chroot_exists = os.path.exists(options.chroot)
602 if options.create or options.enter:
603 # Only create if it's being wiped, or if it doesn't exist.
604 if not options.delete and chroot_exists:
605 options.create = False
606 else:
607 options.download = True
608
609 # Finally, flip create if necessary.
610 if options.enter:
611 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800612
Brian Harringb938c782012-02-29 15:14:38 -0800613 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700614 sdk_version = (bootstrap_latest_version if options.bootstrap
615 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800616 else:
617 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500618 if options.buildbot_log_version:
619 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800620
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700621 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700622 if options.sdk_url:
623 urls = [options.sdk_url]
624 elif options.bootstrap:
625 urls = GetStage3Urls(sdk_version)
626 else:
627 urls = GetArchStageTarballs(sdk_version)
628
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700629 # Get URLs for the board-specific overlay, if one is to be used.
630 overlay_urls = None
631 if options.board and not options.bootstrap:
632 overlay_urls = GetBoardOverlayUrls(sdk_version, options.board)
633
Brian Harringb6cf9142012-09-01 20:43:17 -0700634 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700635 lock_path = os.path.join(
636 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400637 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700638 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700639 board_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700640
Josh Triplett472a4182013-03-08 11:48:57 -0800641 if options.proxy_sim:
642 _ProxySimSetup(options)
643
David James56e6c2c2012-10-24 23:54:41 -0700644 if options.delete and os.path.exists(options.chroot):
645 lock.write_lock()
646 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800647
David James56e6c2c2012-10-24 23:54:41 -0700648 sdk_cache = os.path.join(options.cache_dir, 'sdks')
649 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700650 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700651
David James56e6c2c2012-10-24 23:54:41 -0700652 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500653 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700654 if not os.path.exists(src):
655 osutils.SafeMakedirs(target)
656 continue
657 lock.write_lock(
658 "Upgrade to %r needed but chroot is locked; please exit "
659 "all instances so this upgrade can finish." % src)
660 if not os.path.exists(src):
661 # Note that while waiting for the write lock, src may've vanished;
662 # it's a rare race during the upgrade process that's a byproduct
663 # of us avoiding taking a write lock to do the src check. If we
664 # took a write lock for that check, it would effectively limit
665 # all cros_sdk for a chroot to a single instance.
666 osutils.SafeMakedirs(target)
667 elif not os.path.exists(target):
668 # Upgrade occurred, but a reversion, or something whacky
669 # occurred writing to the old location. Wipe and continue.
670 os.rename(src, target)
671 else:
672 # Upgrade occurred once already, but either a reversion or
673 # some before/after separate cros_sdk usage is at play.
674 # Wipe and continue.
675 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700676
David James56e6c2c2012-10-24 23:54:41 -0700677 if options.download:
678 lock.write_lock()
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700679 desc = 'stage3 tarball' if options.bootstrap else 'SDK tarball'
680 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls, desc)
681 if overlay_urls:
682 board_overlay_tarball = FetchRemoteTarballs(sdk_cache, overlay_urls,
683 'board overlay tarball',
684 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700685
David James56e6c2c2012-10-24 23:54:41 -0700686 if options.create:
687 lock.write_lock()
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700688 CreateChroot(options.chroot, sdk_tarball, board_overlay_tarball,
689 options.cache_dir,
David Purselld99f6222015-05-04 16:52:59 -0700690 nousepkg=(options.bootstrap or options.nousepkg),
691 workspace=options.workspace)
Brian Harring1790ac42012-09-23 08:53:33 -0700692
David James56e6c2c2012-10-24 23:54:41 -0700693 if options.enter:
694 lock.read_lock()
695 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700696 options.chrome_root_mount, options.workspace,
697 chroot_command)