blob: 3d70ff17b9189cdab056ef7d33408215ae932dd4 [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 Arnoldecc86fa2015-05-22 12:06:04 -070080def GetBoardOverlayUrls(version, board):
81 """Returns the URL(s) for the board-specific toolchain overlay.
82
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
86 board overlays are stored (.../2015/05/ in this case).
87 board: The board name.
88
89 Returns:
90 List of alternative download URLs for the board's SDK overlay tarball.
91 """
92 suburl_template = os.path.join(
93 *(version.split('.')[:2] +
94 ['cros-sdk-overlay-%s-%s.tar.%%s' % (board, version)]))
95 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
96 for ext in COMPRESSION_PREFERENCE]
97
98
99def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500100 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200101
102 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500103 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200104 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700105 desc: A string describing what's the thing we're downloading (for logging).
106 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200107
108 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700109 Full path to the downloaded file, or None if |allow_none| and no URL worked.
110
111 Raises:
112 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200113 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200114
Brian Harring1790ac42012-09-23 08:53:33 -0700115 # Note we track content length ourselves since certain versions of curl
116 # fail if asked to resume a complete file.
117 # pylint: disable=C0301,W0631
118 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700119 logging.notice('Downloading %s...', desc)
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200120 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700121 # http://www.logilab.org/ticket/8766
122 # pylint: disable=E1101
123 parsed = urlparse.urlparse(url)
124 tarball_name = os.path.basename(parsed.path)
125 if parsed.scheme in ('', 'file'):
126 if os.path.exists(parsed.path):
127 return parsed.path
128 continue
129 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700130 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800131 result = retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400132 ['-I', url], fail=False, redirect_stdout=True, redirect_stderr=True,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500133 print_cmd=False)
Brian Harring1790ac42012-09-23 08:53:33 -0700134 successful = False
135 for header in result.output.splitlines():
136 # We must walk the output to find the string '200 OK' for use cases where
137 # a proxy is involved and may have pushed down the actual header.
138 if header.find('200 OK') != -1:
139 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500140 elif header.lower().startswith('content-length:'):
141 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700142 if successful:
143 break
144 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200145 break
146 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700147 if allow_none:
148 return None
149 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200150
Brian Harringae0a5322012-09-15 01:46:51 -0700151 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700152 current_size = 0
153 if os.path.exists(tarball_dest):
154 current_size = os.path.getsize(tarball_dest)
155 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700156 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700157 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100158
Brian Harring1790ac42012-09-23 08:53:33 -0700159 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800160 retry_util.RunCurl(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400161 ['-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Brian Harring1790ac42012-09-23 08:53:33 -0700162 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800163
Brian Harring1790ac42012-09-23 08:53:33 -0700164 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700165 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
166 # because we might have partial overlap between known prefixes.
167 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
168 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
169 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700170 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700171 if (filename == tarball_name or
172 any([(filename.startswith(p) and
173 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
174 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700175 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700176 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700177 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200178
Brian Harringb938c782012-02-29 15:14:38 -0800179 return tarball_dest
180
181
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700182def CreateChroot(chroot_path, sdk_tarball, board_overlay_tarball, cache_dir,
183 nousepkg=False, workspace=None):
Brian Harringb938c782012-02-29 15:14:38 -0800184 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800185
Brian Harring1790ac42012-09-23 08:53:33 -0700186 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700187 '--chroot', chroot_path,
188 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700189
190 if board_overlay_tarball:
191 cmd.extend(['--board_overlay_path', board_overlay_tarball])
192
Mike Frysinger2de7f042012-07-10 04:45:03 -0400193 if nousepkg:
194 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800195
David Purselld99f6222015-05-04 16:52:59 -0700196 if workspace:
197 cmd.extend(['--workspace_root', workspace])
198
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.')
502 parser.add_argument('--board',
503 help='The board we intend to be building in the chroot. '
504 'Used for downloading an SDK board overlay (if one is '
505 'found), which may speed up chroot initialization when '
506 'building for the first time. Otherwise this has no '
507 'effect and will not restrict the chroot in any way. '
508 'Ignored if using --bootstrap.')
509 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500510
511 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400512 group = parser.add_argument_group('Commands')
513 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500514 '--enter', action='store_true', default=False,
515 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400516 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500517 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500518 help='Create the chroot only if it does not already exist. '
519 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400520 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500521 '--bootstrap', action='store_true', default=False,
522 help='Build everything from scratch, including the sdk. '
523 'Use this only if you need to validate a change '
524 'that affects SDK creation itself (toolchain and '
525 'build are typically the only folk who need this). '
526 'Note this will quite heavily slow down the build. '
527 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400528 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500529 '-r', '--replace', action='store_true', default=False,
530 help='Replace an existing SDK chroot. Basically an alias '
531 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400532 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500533 '--delete', action='store_true', default=False,
534 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400535 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500536 '--download', action='store_true', default=False,
537 help='Download the sdk.')
538 commands = group
539
Mike Frysinger80dfce92014-04-21 10:58:53 -0400540 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400541 group = parser.add_argument_group('Namespaces')
542 group.add_argument('--proxy-sim', action='store_true', default=False,
543 help='Simulate a restrictive network requiring an outbound'
544 ' proxy.')
545 group.add_argument('--no-ns-pid', dest='ns_pid',
546 default=True, action='store_false',
547 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400548
Mike Frysinger34db8692013-11-11 14:54:08 -0500549 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400550 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500551 'Internal Chromium OS Build Team Options',
552 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400553 group.add_argument('--buildbot-log-version', default=False,
554 action='store_true',
555 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500556
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400557 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500558
559
560def main(argv):
561 conf = cros_build_lib.LoadKeyValueFile(
562 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
563 ignore_missing=True)
564 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
565 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
566 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400567 options = parser.parse_args(argv)
568 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800569
570 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500571 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800572
Brian Harring1790ac42012-09-23 08:53:33 -0700573 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700574 if host != 'x86_64':
575 parser.error(
576 "cros_sdk is currently only supported on x86_64; you're running"
577 " %s. Please find a x86_64 machine." % (host,))
578
Josh Triplett472a4182013-03-08 11:48:57 -0800579 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
580 if options.proxy_sim:
581 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800582
David James471532c2013-01-21 10:23:31 -0800583 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400584 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400585 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400586 else:
587 first_pid = None
David James471532c2013-01-21 10:23:31 -0800588
Brian Harring218e13c2012-10-10 16:21:26 -0700589 # Expand out the aliases...
590 if options.replace:
591 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800592
Brian Harring218e13c2012-10-10 16:21:26 -0700593 if options.bootstrap:
594 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800595
Brian Harring218e13c2012-10-10 16:21:26 -0700596 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400597 # pylint: disable=protected-access
598 # This _group_actions access sucks, but upstream decided to not include an
599 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700600 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400601 for x in commands._group_actions)
602 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700603 options.enter |= bool(chroot_command)
604
605 if options.enter and options.delete and not options.create:
606 parser.error("Trying to enter the chroot when --delete "
607 "was specified makes no sense.")
608
609 # Finally, discern if we need to create the chroot.
610 chroot_exists = os.path.exists(options.chroot)
611 if options.create or options.enter:
612 # Only create if it's being wiped, or if it doesn't exist.
613 if not options.delete and chroot_exists:
614 options.create = False
615 else:
616 options.download = True
617
618 # Finally, flip create if necessary.
619 if options.enter:
620 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800621
Brian Harringb938c782012-02-29 15:14:38 -0800622 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700623 sdk_version = (bootstrap_latest_version if options.bootstrap
624 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800625 else:
626 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500627 if options.buildbot_log_version:
628 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800629
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700630 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700631 if options.sdk_url:
632 urls = [options.sdk_url]
633 elif options.bootstrap:
634 urls = GetStage3Urls(sdk_version)
635 else:
636 urls = GetArchStageTarballs(sdk_version)
637
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700638 # Get URLs for the board-specific overlay, if one is to be used.
639 overlay_urls = None
640 if options.board and not options.bootstrap:
641 overlay_urls = GetBoardOverlayUrls(sdk_version, options.board)
642
Brian Harringb6cf9142012-09-01 20:43:17 -0700643 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700644 lock_path = os.path.join(
645 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400646 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700647 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700648 board_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700649
Josh Triplett472a4182013-03-08 11:48:57 -0800650 if options.proxy_sim:
651 _ProxySimSetup(options)
652
David James56e6c2c2012-10-24 23:54:41 -0700653 if options.delete and os.path.exists(options.chroot):
654 lock.write_lock()
655 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800656
David James56e6c2c2012-10-24 23:54:41 -0700657 sdk_cache = os.path.join(options.cache_dir, 'sdks')
658 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700659 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700660
David James56e6c2c2012-10-24 23:54:41 -0700661 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500662 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700663 if not os.path.exists(src):
664 osutils.SafeMakedirs(target)
665 continue
666 lock.write_lock(
667 "Upgrade to %r needed but chroot is locked; please exit "
668 "all instances so this upgrade can finish." % src)
669 if not os.path.exists(src):
670 # Note that while waiting for the write lock, src may've vanished;
671 # it's a rare race during the upgrade process that's a byproduct
672 # of us avoiding taking a write lock to do the src check. If we
673 # took a write lock for that check, it would effectively limit
674 # all cros_sdk for a chroot to a single instance.
675 osutils.SafeMakedirs(target)
676 elif not os.path.exists(target):
677 # Upgrade occurred, but a reversion, or something whacky
678 # occurred writing to the old location. Wipe and continue.
679 os.rename(src, target)
680 else:
681 # Upgrade occurred once already, but either a reversion or
682 # some before/after separate cros_sdk usage is at play.
683 # Wipe and continue.
684 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700685
David James56e6c2c2012-10-24 23:54:41 -0700686 if options.download:
687 lock.write_lock()
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700688 desc = 'stage3 tarball' if options.bootstrap else 'SDK tarball'
689 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls, desc)
690 if overlay_urls:
691 board_overlay_tarball = FetchRemoteTarballs(sdk_cache, overlay_urls,
692 'board overlay tarball',
693 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700694
David James56e6c2c2012-10-24 23:54:41 -0700695 if options.create:
696 lock.write_lock()
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700697 CreateChroot(options.chroot, sdk_tarball, board_overlay_tarball,
698 options.cache_dir,
David Purselld99f6222015-05-04 16:52:59 -0700699 nousepkg=(options.bootstrap or options.nousepkg),
700 workspace=options.workspace)
Brian Harring1790ac42012-09-23 08:53:33 -0700701
David James56e6c2c2012-10-24 23:54:41 -0700702 if options.enter:
703 lock.read_lock()
704 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700705 options.chrome_root_mount, options.workspace,
706 chroot_command)