blob: d6613b0b255089e4fa3bdc4a061480e531395adf [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(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400135 ['-I', url], fail=False, redirect_stdout=True, redirect_stderr=True,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500136 print_cmd=False)
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],
Brian Harring1790ac42012-09-23 08:53:33 -0700165 print_cmd=False)
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,
186 cache_dir, nousepkg=False, workspace=None):
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
David Purselld99f6222015-05-04 16:52:59 -0700199 if workspace:
200 cmd.extend(['--workspace_root', workspace])
201
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700202 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800203 try:
204 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
209def DeleteChroot(chroot_path):
210 """Deletes an existing chroot"""
211 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
212 '--delete']
213 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700214 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800215 cros_build_lib.RunCommand(cmd, print_cmd=False)
216 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700217 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800218
219
Brian Harringae0a5322012-09-15 01:46:51 -0700220def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700221 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800222 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400223 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
224 # The os.ST_NOSUID constant wasn't added until python-3.2.
225 if st.f_flag & 0x2:
226 cros_build_lib.Die('chroot cannot be in a nosuid mount')
227
Brian Harringae0a5322012-09-15 01:46:51 -0700228 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800229 if chrome_root:
230 cmd.extend(['--chrome_root', chrome_root])
231 if chrome_root_mount:
232 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700233 if workspace:
234 cmd.extend(['--workspace_root', workspace])
235
Brian Harringb938c782012-02-29 15:14:38 -0800236 if len(additional_args) > 0:
237 cmd.append('--')
238 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700239
Ralph Nathan549d3502015-03-26 17:38:42 -0700240 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
241 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700242 # If we were in interactive mode, ignore the exit code; it'll be whatever
243 # they last ran w/in the chroot and won't matter to us one way or another.
244 # Note this does allow chroot entrance to fail and be ignored during
245 # interactive; this is however a rare case and the user will immediately
246 # see it (nor will they be checking the exit code manually).
247 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700248 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800249
250
David James56e6c2c2012-10-24 23:54:41 -0700251def _SudoCommand():
252 """Get the 'sudo' command, along with all needed environment variables."""
253
David James5a73b4d2013-03-07 10:23:40 -0800254 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
255 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700256 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800257 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700258 value = os.environ.get(key)
259 if value is not None:
260 cmd += ['%s=%s' % (key, value)]
261
262 # Pass in the path to the depot_tools so that users can access them from
263 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400264 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500265
David James56e6c2c2012-10-24 23:54:41 -0700266 return cmd
267
268
Josh Triplett472a4182013-03-08 11:48:57 -0800269def _ReportMissing(missing):
270 """Report missing utilities, then exit.
271
272 Args:
273 missing: List of missing utilities, as returned by
274 osutils.FindMissingBinaries. If non-empty, will not return.
275 """
276
277 if missing:
278 raise SystemExit(
279 'The tool(s) %s were not found.\n'
280 'Please install the appropriate package in your host.\n'
281 'Example(ubuntu):\n'
282 ' sudo apt-get install <packagename>'
283 % ', '.join(missing))
284
285
286def _ProxySimSetup(options):
287 """Set up proxy simulator, and return only in the child environment.
288
289 TODO: Ideally, this should support multiple concurrent invocations of
290 cros_sdk --proxy-sim; currently, such invocations will conflict with each
291 other due to the veth device names and IP addresses. Either this code would
292 need to generate fresh, unused names for all of these before forking, or it
293 would need to support multiple concurrent cros_sdk invocations sharing one
294 proxy and allowing it to exit when unused (without counting on any local
295 service-management infrastructure on the host).
296 """
297
298 may_need_mpm = False
299 apache_bin = osutils.Which('apache2')
300 if apache_bin is None:
301 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
302 if apache_bin is None:
303 _ReportMissing(('apache2',))
304 else:
305 may_need_mpm = True
306
307 # Module names and .so names included for ease of grepping.
308 apache_modules = [('proxy_module', 'mod_proxy.so'),
309 ('proxy_connect_module', 'mod_proxy_connect.so'),
310 ('proxy_http_module', 'mod_proxy_http.so'),
311 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
312
313 # Find the apache module directory, and make sure it has the modules we need.
314 module_dirs = {}
315 for g in PROXY_APACHE_MODULE_GLOBS:
316 for mod, so in apache_modules:
317 for f in glob.glob(os.path.join(g, so)):
318 module_dirs.setdefault(os.path.dirname(f), []).append(so)
319 for apache_module_path, modules_found in module_dirs.iteritems():
320 if len(modules_found) == len(apache_modules):
321 break
322 else:
323 # Appease cros lint, which doesn't understand that this else block will not
324 # fall through to the subsequent code which relies on apache_module_path.
325 apache_module_path = None
326 raise SystemExit(
327 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500328 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800329
330 def check_add_module(name):
331 so = 'mod_%s.so' % name
332 if os.access(os.path.join(apache_module_path, so), os.F_OK):
333 mod = '%s_module' % name
334 apache_modules.append((mod, so))
335 return True
336 return False
337
338 check_add_module('authz_core')
339 if may_need_mpm:
340 for mpm in PROXY_APACHE_MPMS:
341 if check_add_module('mpm_%s' % mpm):
342 break
343
344 veth_host = '%s-host' % PROXY_VETH_PREFIX
345 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
346
347 # Set up pipes from parent to child and vice versa.
348 # The child writes a byte to the parent after calling unshare, so that the
349 # parent can then assign the guest end of the veth interface to the child's
350 # new network namespace. The parent then writes a byte to the child after
351 # assigning the guest interface, so that the child can then configure that
352 # interface. In both cases, if we get back an EOF when reading from the
353 # pipe, we assume the other end exited with an error message, so just exit.
354 parent_readfd, child_writefd = os.pipe()
355 child_readfd, parent_writefd = os.pipe()
356 SUCCESS_FLAG = '+'
357
358 pid = os.fork()
359 if not pid:
360 os.close(parent_readfd)
361 os.close(parent_writefd)
362
363 namespaces.Unshare(namespaces.CLONE_NEWNET)
364 os.write(child_writefd, SUCCESS_FLAG)
365 os.close(child_writefd)
366 if os.read(child_readfd, 1) != SUCCESS_FLAG:
367 # Parent failed; it will have already have outputted an error message.
368 sys.exit(1)
369 os.close(child_readfd)
370
371 # Set up child side of the network.
372 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500373 ('ip', 'link', 'set', 'up', 'lo'),
374 ('ip', 'address', 'add',
375 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
376 'dev', veth_guest),
377 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800378 )
379 try:
380 for cmd in commands:
381 cros_build_lib.RunCommand(cmd, print_cmd=False)
382 except cros_build_lib.RunCommandError:
383 raise SystemExit('Running %r failed!' % (cmd,))
384
385 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
386 for proto in ('http', 'https', 'ftp'):
387 os.environ[proto + '_proxy'] = proxy_url
388 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
389 os.environ.pop(v, None)
390 return
391
392 os.close(child_readfd)
393 os.close(child_writefd)
394
395 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
396 # Child failed; it will have already have outputted an error message.
397 sys.exit(1)
398 os.close(parent_readfd)
399
400 # Set up parent side of the network.
401 uid = int(os.environ.get('SUDO_UID', '0'))
402 gid = int(os.environ.get('SUDO_GID', '0'))
403 if uid == 0 or gid == 0:
404 for username in PROXY_APACHE_FALLBACK_USERS:
405 try:
406 pwnam = pwd.getpwnam(username)
407 uid, gid = pwnam.pw_uid, pwnam.pw_gid
408 break
409 except KeyError:
410 continue
411 if uid == 0 or gid == 0:
412 raise SystemExit('Could not find a non-root user to run Apache as')
413
414 chroot_parent, chroot_base = os.path.split(options.chroot)
415 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
416 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
417
418 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500419 'User #%u' % uid,
420 'Group #%u' % gid,
421 'PidFile %s' % pid_file,
422 'ErrorLog %s' % log_file,
423 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
424 'ServerName %s' % PROXY_HOST_IP,
425 'ProxyRequests On',
426 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800427 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500428 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
429 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800430 ]
431 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500432 ('ip', 'link', 'add', 'name', veth_host,
433 'type', 'veth', 'peer', 'name', veth_guest),
434 ('ip', 'address', 'add',
435 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
436 'dev', veth_host),
437 ('ip', 'link', 'set', veth_host, 'up'),
438 ([apache_bin, '-f', '/dev/null'] +
439 [arg for d in apache_directives for arg in ('-C', d)]),
440 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800441 )
442 cmd = None # Make cros lint happy.
443 try:
444 for cmd in commands:
445 cros_build_lib.RunCommand(cmd, print_cmd=False)
446 except cros_build_lib.RunCommandError:
447 # Clean up existing interfaces, if any.
448 cmd_cleanup = ('ip', 'link', 'del', veth_host)
449 try:
450 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
451 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700452 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800453 raise SystemExit('Running %r failed!' % (cmd,))
454 os.write(parent_writefd, SUCCESS_FLAG)
455 os.close(parent_writefd)
456
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400457 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800458
459
Mike Frysingera78a56e2012-11-20 06:02:30 -0500460def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700461 """Re-execute cros_sdk as root.
462
463 Also unshare the mount namespace so as to ensure that processes outside
464 the chroot can't mess with our mounts.
465 """
466 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500467 cmd = _SudoCommand() + ['--'] + argv
468 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500469 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400470 # We must set up the cgroups mounts before we enter our own namespace.
471 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800472 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500473 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700474
475
Mike Frysinger34db8692013-11-11 14:54:08 -0500476def _CreateParser(sdk_latest_version, bootstrap_latest_version):
477 """Generate and return the parser with all the options."""
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400478 usage = ('usage: %(prog)s [options] '
479 '[VAR1=val1 ... VAR2=val2] [--] [command [args]]')
480 parser = commandline.ArgumentParser(usage=usage, description=__doc__,
481 caching=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700482
Mike Frysinger34db8692013-11-11 14:54:08 -0500483 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500484 default_chroot = os.path.join(constants.SOURCE_ROOT,
485 constants.DEFAULT_CHROOT_DIR)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400486 parser.add_argument(
Brian Harring218e13c2012-10-10 16:21:26 -0700487 '--chroot', dest='chroot', default=default_chroot, type='path',
488 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800489
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400490 parser.add_argument('--chrome_root', type='path',
491 help='Mount this chrome root into the SDK chroot')
492 parser.add_argument('--chrome_root_mount', type='path',
493 help='Mount chrome into this path inside SDK chroot')
494 parser.add_argument('--nousepkg', action='store_true', default=False,
495 help='Do not use binary packages when creating a chroot.')
496 parser.add_argument('-u', '--url', dest='sdk_url',
497 help='Use sdk tarball located at this url. Use file:// '
498 'for local files.')
499 parser.add_argument('--sdk-version',
500 help=('Use this sdk version. For prebuilt, current is %r'
501 ', for bootstrapping it is %r.'
502 % (sdk_latest_version, bootstrap_latest_version)))
503 parser.add_argument('--workspace',
504 help='Workspace directory to mount into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400505 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500506
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700507 # SDK overlay tarball options (mutually exclusive).
508 group = parser.add_mutually_exclusive_group()
509 group.add_argument('--toolchains',
510 help=('Comma-separated list of toolchains we expect to be '
511 'using on the chroot. Used for downloading a '
512 'corresponding SDK toolchains group (if one is '
513 'found), which may speed up chroot initialization '
514 'when building for the first time. Otherwise this '
515 'has no effect and will not restrict the chroot in '
516 'any way. Ignored if using --bootstrap.'))
517 group.add_argument('--board',
518 help=('The board we intend to be building in the chroot. '
519 'Used for deriving the list of required toolchains '
520 '(see --toolchains).'))
521
Mike Frysinger34db8692013-11-11 14:54:08 -0500522 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400523 group = parser.add_argument_group('Commands')
524 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500525 '--enter', action='store_true', default=False,
526 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400527 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500528 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500529 help='Create the chroot only if it does not already exist. '
530 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400531 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500532 '--bootstrap', action='store_true', default=False,
533 help='Build everything from scratch, including the sdk. '
534 'Use this only if you need to validate a change '
535 'that affects SDK creation itself (toolchain and '
536 'build are typically the only folk who need this). '
537 'Note this will quite heavily slow down the build. '
538 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400539 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500540 '-r', '--replace', action='store_true', default=False,
541 help='Replace an existing SDK chroot. Basically an alias '
542 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400543 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500544 '--delete', action='store_true', default=False,
545 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400546 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500547 '--download', action='store_true', default=False,
548 help='Download the sdk.')
549 commands = group
550
Mike Frysinger80dfce92014-04-21 10:58:53 -0400551 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400552 group = parser.add_argument_group('Namespaces')
553 group.add_argument('--proxy-sim', action='store_true', default=False,
554 help='Simulate a restrictive network requiring an outbound'
555 ' proxy.')
556 group.add_argument('--no-ns-pid', dest='ns_pid',
557 default=True, action='store_false',
558 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400559
Mike Frysinger34db8692013-11-11 14:54:08 -0500560 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400561 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500562 'Internal Chromium OS Build Team Options',
563 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400564 group.add_argument('--buildbot-log-version', default=False,
565 action='store_true',
566 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500567
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400568 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500569
570
571def main(argv):
572 conf = cros_build_lib.LoadKeyValueFile(
573 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
574 ignore_missing=True)
575 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
576 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
577 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400578 options = parser.parse_args(argv)
579 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800580
581 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500582 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800583
Brian Harring1790ac42012-09-23 08:53:33 -0700584 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700585 if host != 'x86_64':
586 parser.error(
587 "cros_sdk is currently only supported on x86_64; you're running"
588 " %s. Please find a x86_64 machine." % (host,))
589
Josh Triplett472a4182013-03-08 11:48:57 -0800590 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
591 if options.proxy_sim:
592 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800593
David James471532c2013-01-21 10:23:31 -0800594 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400595 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400596 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400597 else:
598 first_pid = None
David James471532c2013-01-21 10:23:31 -0800599
Brian Harring218e13c2012-10-10 16:21:26 -0700600 # Expand out the aliases...
601 if options.replace:
602 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800603
Brian Harring218e13c2012-10-10 16:21:26 -0700604 if options.bootstrap:
605 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800606
Brian Harring218e13c2012-10-10 16:21:26 -0700607 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400608 # pylint: disable=protected-access
609 # This _group_actions access sucks, but upstream decided to not include an
610 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700611 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400612 for x in commands._group_actions)
613 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700614 options.enter |= bool(chroot_command)
615
616 if options.enter and options.delete and not options.create:
617 parser.error("Trying to enter the chroot when --delete "
618 "was specified makes no sense.")
619
620 # Finally, discern if we need to create the chroot.
621 chroot_exists = os.path.exists(options.chroot)
622 if options.create or options.enter:
623 # Only create if it's being wiped, or if it doesn't exist.
624 if not options.delete and chroot_exists:
625 options.create = False
626 else:
627 options.download = True
628
629 # Finally, flip create if necessary.
630 if options.enter:
631 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800632
Brian Harringb938c782012-02-29 15:14:38 -0800633 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700634 sdk_version = (bootstrap_latest_version if options.bootstrap
635 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800636 else:
637 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500638 if options.buildbot_log_version:
639 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800640
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700641 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700642 if options.sdk_url:
643 urls = [options.sdk_url]
644 elif options.bootstrap:
645 urls = GetStage3Urls(sdk_version)
646 else:
647 urls = GetArchStageTarballs(sdk_version)
648
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700649 # Get URLs for the toolchains overlay, if one is to be used.
650 toolchains_overlay_urls = None
651 if not options.bootstrap:
652 toolchains = None
653 if options.toolchains:
654 toolchains = options.toolchains.split(',')
655 elif options.board:
656 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
657
658 if toolchains:
659 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
660 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700661
Brian Harringb6cf9142012-09-01 20:43:17 -0700662 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700663 lock_path = os.path.join(
664 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400665 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700666 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700667 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700668
Josh Triplett472a4182013-03-08 11:48:57 -0800669 if options.proxy_sim:
670 _ProxySimSetup(options)
671
David James56e6c2c2012-10-24 23:54:41 -0700672 if options.delete and os.path.exists(options.chroot):
673 lock.write_lock()
674 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800675
David James56e6c2c2012-10-24 23:54:41 -0700676 sdk_cache = os.path.join(options.cache_dir, 'sdks')
677 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700678 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700679
David James56e6c2c2012-10-24 23:54:41 -0700680 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500681 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700682 if not os.path.exists(src):
683 osutils.SafeMakedirs(target)
684 continue
685 lock.write_lock(
686 "Upgrade to %r needed but chroot is locked; please exit "
687 "all instances so this upgrade can finish." % src)
688 if not os.path.exists(src):
689 # Note that while waiting for the write lock, src may've vanished;
690 # it's a rare race during the upgrade process that's a byproduct
691 # of us avoiding taking a write lock to do the src check. If we
692 # took a write lock for that check, it would effectively limit
693 # all cros_sdk for a chroot to a single instance.
694 osutils.SafeMakedirs(target)
695 elif not os.path.exists(target):
696 # Upgrade occurred, but a reversion, or something whacky
697 # occurred writing to the old location. Wipe and continue.
698 os.rename(src, target)
699 else:
700 # Upgrade occurred once already, but either a reversion or
701 # some before/after separate cros_sdk usage is at play.
702 # Wipe and continue.
703 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700704
David James56e6c2c2012-10-24 23:54:41 -0700705 if options.download:
706 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700707 sdk_tarball = FetchRemoteTarballs(
708 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
709 if toolchains_overlay_urls:
710 toolchains_overlay_tarball = FetchRemoteTarballs(
711 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
712 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700713
David James56e6c2c2012-10-24 23:54:41 -0700714 if options.create:
715 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700716 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700717 options.cache_dir,
David Purselld99f6222015-05-04 16:52:59 -0700718 nousepkg=(options.bootstrap or options.nousepkg),
719 workspace=options.workspace)
Brian Harring1790ac42012-09-23 08:53:33 -0700720
David James56e6c2c2012-10-24 23:54:41 -0700721 if options.enter:
722 lock.read_lock()
723 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700724 options.chrome_root_mount, options.workspace,
725 chroot_command)