blob: 7558d19fb975abbb31027fda229b9d42866a752d [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
Brian Norrisd37e2f72016-08-22 16:09:24 -070020import re
David James56e6c2c2012-10-24 23:54:41 -070021import sys
Brian Harringb938c782012-02-29 15:14:38 -080022import urlparse
23
Aviv Keshetb7519e12016-10-04 00:50:00 -070024from chromite.lib import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080025from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070026from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080027from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070028from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080029from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080030from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070031from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040032from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080033from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050034from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080035
36cros_build_lib.STRICT_SUDO = True
37
38
Zdenek Behanaa52cea2012-05-30 01:31:11 +020039COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020040
Brian Harringb938c782012-02-29 15:14:38 -080041# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050042MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
43 'src/scripts/sdk_lib/make_chroot.sh')]
44ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
45 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080046
Josh Triplett472a4182013-03-08 11:48:57 -080047# Proxy simulator configuration.
48PROXY_HOST_IP = '192.168.240.1'
49PROXY_PORT = 8080
50PROXY_GUEST_IP = '192.168.240.2'
51PROXY_NETMASK = 30
52PROXY_VETH_PREFIX = 'veth'
53PROXY_CONNECT_PORTS = (80, 443, 9418)
54PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
55PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
56PROXY_APACHE_FALLBACK_PATH = ':'.join(
57 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
58)
59PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
60
Josh Triplett9a495f62013-03-15 18:06:55 -070061# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080062NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080063
Josh Triplett472a4182013-03-08 11:48:57 -080064# Tools needed for --proxy-sim only.
65PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080066
Mike Frysingercc838832014-05-24 13:10:30 -040067
Brian Harring1790ac42012-09-23 08:53:33 -070068def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080069 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070070 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050071 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
72 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070073 for compressor in COMPRESSION_PREFERENCE]
74
75
76def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050077 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070078 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080079
80
Gilad Arnold6a8f0452015-06-04 11:25:18 -070081def GetToolchainsOverlayUrls(version, toolchains):
82 """Returns the URL(s) for a toolchains SDK overlay.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070083
84 Args:
85 version: The SDK version used, e.g. 2015.05.27.145939. We use the year and
86 month components to point to a subdirectory on the SDK bucket where
Gilad Arnold6a8f0452015-06-04 11:25:18 -070087 overlays are stored (.../2015/05/ in this case).
88 toolchains: Iterable of toolchain target strings (e.g. 'i686-pc-linux-gnu').
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070089
90 Returns:
Gilad Arnold6a8f0452015-06-04 11:25:18 -070091 List of alternative download URLs for an SDK overlay tarball that contains
92 the given toolchains.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070093 """
Gilad Arnold6a8f0452015-06-04 11:25:18 -070094 toolchains_desc = '-'.join(sorted(toolchains))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070095 suburl_template = os.path.join(
96 *(version.split('.')[:2] +
Gilad Arnold6a8f0452015-06-04 11:25:18 -070097 ['cros-sdk-overlay-toolchains-%s-%s.tar.%%s' %
98 (toolchains_desc, version)]))
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070099 return [toolchain.GetSdkURL(suburl=suburl_template % ext)
100 for ext in COMPRESSION_PREFERENCE]
101
102
103def FetchRemoteTarballs(storage_dir, urls, desc, allow_none=False):
Mike Frysinger34db8692013-11-11 14:54:08 -0500104 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200105
106 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -0500107 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200108 urls: List of URLs to try to download. Download will stop on first success.
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700109 desc: A string describing what tarball we're downloading (for logging).
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700110 allow_none: Don't fail if none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111
112 Returns:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700113 Full path to the downloaded file, or None if |allow_none| and no URL worked.
114
115 Raises:
116 ValueError: If |allow_none| is False and none of the URLs worked.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200117 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200118
Brian Harring1790ac42012-09-23 08:53:33 -0700119 # Note we track content length ourselves since certain versions of curl
120 # fail if asked to resume a complete file.
121 # pylint: disable=C0301,W0631
122 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700123 logging.notice('Downloading %s tarball...', desc)
Brian Norriscf8aef42016-09-27 10:43:39 -0700124 status_re = re.compile(r'^HTTP/[0-9]+(\.[0-9]+)? 200')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200125 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700126 # http://www.logilab.org/ticket/8766
127 # pylint: disable=E1101
128 parsed = urlparse.urlparse(url)
129 tarball_name = os.path.basename(parsed.path)
130 if parsed.scheme in ('', 'file'):
131 if os.path.exists(parsed.path):
132 return parsed.path
133 continue
134 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700135 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800136 result = retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900137 ['-I', url], print_cmd=False, debug_level=logging.NOTICE,
138 capture_output=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700139 successful = False
140 for header in result.output.splitlines():
Brian Norrisd37e2f72016-08-22 16:09:24 -0700141 # We must walk the output to find the 200 code for use cases where
Brian Harring1790ac42012-09-23 08:53:33 -0700142 # a proxy is involved and may have pushed down the actual header.
Brian Norrisd37e2f72016-08-22 16:09:24 -0700143 if status_re.match(header):
Brian Harring1790ac42012-09-23 08:53:33 -0700144 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500145 elif header.lower().startswith('content-length:'):
146 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700147 if successful:
148 break
149 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200150 break
151 else:
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700152 if allow_none:
153 return None
154 raise ValueError('No valid URLs found!')
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200155
Brian Harringae0a5322012-09-15 01:46:51 -0700156 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700157 current_size = 0
158 if os.path.exists(tarball_dest):
159 current_size = os.path.getsize(tarball_dest)
160 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700161 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700162 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100163
Brian Harring1790ac42012-09-23 08:53:33 -0700164 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800165 retry_util.RunCurl(
Hidehiko Abee55af7f2017-05-01 18:38:04 +0900166 ['--fail', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
167 print_cmd=False, debug_level=logging.NOTICE)
Brian Harringb938c782012-02-29 15:14:38 -0800168
Brian Harring1790ac42012-09-23 08:53:33 -0700169 # Cleanup old tarballs now since we've successfull fetched; only cleanup
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700170 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
171 # because we might have partial overlap between known prefixes.
172 my_prefix = tarball_name.rsplit('-', 1)[0] + '-'
173 all_prefixes = ('stage3-amd64-', 'cros-sdk-', 'cros-sdk-overlay-')
174 ignored_prefixes = [prefix for prefix in all_prefixes if prefix != my_prefix]
Brian Harring1790ac42012-09-23 08:53:33 -0700175 for filename in os.listdir(storage_dir):
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700176 if (filename == tarball_name or
177 any([(filename.startswith(p) and
178 not (len(my_prefix) > len(p) and filename.startswith(my_prefix)))
179 for p in ignored_prefixes])):
Brian Harring1790ac42012-09-23 08:53:33 -0700180 continue
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700181 logging.info('Cleaning up old tarball: %s', filename)
David James56e6c2c2012-10-24 23:54:41 -0700182 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200183
Brian Harringb938c782012-02-29 15:14:38 -0800184 return tarball_dest
185
186
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700187def CreateChroot(chroot_path, sdk_tarball, toolchains_overlay_tarball,
David Pursell3528e8e2015-07-07 11:22:15 -0700188 cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800189 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800190
Brian Harring1790ac42012-09-23 08:53:33 -0700191 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700192 '--chroot', chroot_path,
193 '--cache_dir', cache_dir]
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700194
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700195 if toolchains_overlay_tarball:
196 cmd.extend(['--toolchains_overlay_path', toolchains_overlay_tarball])
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700197
Mike Frysinger2de7f042012-07-10 04:45:03 -0400198 if nousepkg:
199 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800200
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700201 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800202 try:
203 cros_build_lib.RunCommand(cmd, print_cmd=False)
204 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700205 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800206
207
208def DeleteChroot(chroot_path):
209 """Deletes an existing chroot"""
210 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
211 '--delete']
212 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700213 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800214 cros_build_lib.RunCommand(cmd, print_cmd=False)
215 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700216 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800217
218
Brian Harringae0a5322012-09-15 01:46:51 -0700219def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900220 workspace, goma_dir, goma_client_json, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800221 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400222 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
223 # The os.ST_NOSUID constant wasn't added until python-3.2.
224 if st.f_flag & 0x2:
225 cros_build_lib.Die('chroot cannot be in a nosuid mount')
226
Brian Harringae0a5322012-09-15 01:46:51 -0700227 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800228 if chrome_root:
229 cmd.extend(['--chrome_root', chrome_root])
230 if chrome_root_mount:
231 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700232 if workspace:
233 cmd.extend(['--workspace_root', workspace])
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900234 if goma_dir:
235 cmd.extend(['--goma_dir', goma_dir])
236 if goma_client_json:
237 cmd.extend(['--goma_client_json', goma_client_json])
Don Garrett230d1b22015-03-09 16:21:19 -0700238
Brian Harringb938c782012-02-29 15:14:38 -0800239 if len(additional_args) > 0:
240 cmd.append('--')
241 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700242
Ralph Nathan549d3502015-03-26 17:38:42 -0700243 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
244 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700245 # If we were in interactive mode, ignore the exit code; it'll be whatever
246 # they last ran w/in the chroot and won't matter to us one way or another.
247 # Note this does allow chroot entrance to fail and be ignored during
248 # interactive; this is however a rare case and the user will immediately
249 # see it (nor will they be checking the exit code manually).
250 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700251 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800252
253
David James56e6c2c2012-10-24 23:54:41 -0700254def _SudoCommand():
255 """Get the 'sudo' command, along with all needed environment variables."""
256
David James5a73b4d2013-03-07 10:23:40 -0800257 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
258 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700259 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800260 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700261 value = os.environ.get(key)
262 if value is not None:
263 cmd += ['%s=%s' % (key, value)]
264
265 # Pass in the path to the depot_tools so that users can access them from
266 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400267 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500268
David James56e6c2c2012-10-24 23:54:41 -0700269 return cmd
270
271
Josh Triplett472a4182013-03-08 11:48:57 -0800272def _ReportMissing(missing):
273 """Report missing utilities, then exit.
274
275 Args:
276 missing: List of missing utilities, as returned by
277 osutils.FindMissingBinaries. If non-empty, will not return.
278 """
279
280 if missing:
281 raise SystemExit(
282 'The tool(s) %s were not found.\n'
283 'Please install the appropriate package in your host.\n'
284 'Example(ubuntu):\n'
285 ' sudo apt-get install <packagename>'
286 % ', '.join(missing))
287
288
289def _ProxySimSetup(options):
290 """Set up proxy simulator, and return only in the child environment.
291
292 TODO: Ideally, this should support multiple concurrent invocations of
293 cros_sdk --proxy-sim; currently, such invocations will conflict with each
294 other due to the veth device names and IP addresses. Either this code would
295 need to generate fresh, unused names for all of these before forking, or it
296 would need to support multiple concurrent cros_sdk invocations sharing one
297 proxy and allowing it to exit when unused (without counting on any local
298 service-management infrastructure on the host).
299 """
300
301 may_need_mpm = False
302 apache_bin = osutils.Which('apache2')
303 if apache_bin is None:
304 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
305 if apache_bin is None:
306 _ReportMissing(('apache2',))
307 else:
308 may_need_mpm = True
309
310 # Module names and .so names included for ease of grepping.
311 apache_modules = [('proxy_module', 'mod_proxy.so'),
312 ('proxy_connect_module', 'mod_proxy_connect.so'),
313 ('proxy_http_module', 'mod_proxy_http.so'),
314 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
315
316 # Find the apache module directory, and make sure it has the modules we need.
317 module_dirs = {}
318 for g in PROXY_APACHE_MODULE_GLOBS:
319 for mod, so in apache_modules:
320 for f in glob.glob(os.path.join(g, so)):
321 module_dirs.setdefault(os.path.dirname(f), []).append(so)
322 for apache_module_path, modules_found in module_dirs.iteritems():
323 if len(modules_found) == len(apache_modules):
324 break
325 else:
326 # Appease cros lint, which doesn't understand that this else block will not
327 # fall through to the subsequent code which relies on apache_module_path.
328 apache_module_path = None
329 raise SystemExit(
330 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500331 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800332
333 def check_add_module(name):
334 so = 'mod_%s.so' % name
335 if os.access(os.path.join(apache_module_path, so), os.F_OK):
336 mod = '%s_module' % name
337 apache_modules.append((mod, so))
338 return True
339 return False
340
341 check_add_module('authz_core')
342 if may_need_mpm:
343 for mpm in PROXY_APACHE_MPMS:
344 if check_add_module('mpm_%s' % mpm):
345 break
346
347 veth_host = '%s-host' % PROXY_VETH_PREFIX
348 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
349
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500350 # Set up locks to sync the net namespace setup. We need the child to create
351 # the net ns first, and then have the parent assign the guest end of the veth
352 # interface to the child's new network namespace & bring up the proxy. Only
353 # then can the child move forward and rely on the network being up.
354 ns_create_lock = locking.PipeLock()
355 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800356
357 pid = os.fork()
358 if not pid:
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500359 # Create our new isolated net namespace.
Josh Triplett472a4182013-03-08 11:48:57 -0800360 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500361
362 # Signal the parent the ns is ready to be configured.
363 ns_create_lock.Post()
364 del ns_create_lock
365
366 # Wait for the parent to finish setting up the ns/proxy.
367 ns_setup_lock.Wait()
368 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800369
370 # Set up child side of the network.
371 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500372 ('ip', 'link', 'set', 'up', 'lo'),
373 ('ip', 'address', 'add',
374 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
375 'dev', veth_guest),
376 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800377 )
378 try:
379 for cmd in commands:
380 cros_build_lib.RunCommand(cmd, print_cmd=False)
381 except cros_build_lib.RunCommandError:
382 raise SystemExit('Running %r failed!' % (cmd,))
383
384 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
385 for proto in ('http', 'https', 'ftp'):
386 os.environ[proto + '_proxy'] = proxy_url
387 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
388 os.environ.pop(v, None)
389 return
390
Josh Triplett472a4182013-03-08 11:48:57 -0800391 # Set up parent side of the network.
392 uid = int(os.environ.get('SUDO_UID', '0'))
393 gid = int(os.environ.get('SUDO_GID', '0'))
394 if uid == 0 or gid == 0:
395 for username in PROXY_APACHE_FALLBACK_USERS:
396 try:
397 pwnam = pwd.getpwnam(username)
398 uid, gid = pwnam.pw_uid, pwnam.pw_gid
399 break
400 except KeyError:
401 continue
402 if uid == 0 or gid == 0:
403 raise SystemExit('Could not find a non-root user to run Apache as')
404
405 chroot_parent, chroot_base = os.path.split(options.chroot)
406 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
407 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
408
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500409 # Wait for the child to create the net ns.
410 ns_create_lock.Wait()
411 del ns_create_lock
412
Josh Triplett472a4182013-03-08 11:48:57 -0800413 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500414 'User #%u' % uid,
415 'Group #%u' % gid,
416 'PidFile %s' % pid_file,
417 'ErrorLog %s' % log_file,
418 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
419 'ServerName %s' % PROXY_HOST_IP,
420 'ProxyRequests On',
421 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800422 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500423 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
424 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800425 ]
426 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500427 ('ip', 'link', 'add', 'name', veth_host,
428 'type', 'veth', 'peer', 'name', veth_guest),
429 ('ip', 'address', 'add',
430 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
431 'dev', veth_host),
432 ('ip', 'link', 'set', veth_host, 'up'),
433 ([apache_bin, '-f', '/dev/null'] +
434 [arg for d in apache_directives for arg in ('-C', d)]),
435 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800436 )
437 cmd = None # Make cros lint happy.
438 try:
439 for cmd in commands:
440 cros_build_lib.RunCommand(cmd, print_cmd=False)
441 except cros_build_lib.RunCommandError:
442 # Clean up existing interfaces, if any.
443 cmd_cleanup = ('ip', 'link', 'del', veth_host)
444 try:
445 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
446 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700447 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800448 raise SystemExit('Running %r failed!' % (cmd,))
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500449
450 # Signal the child that the net ns/proxy is fully configured now.
451 ns_setup_lock.Post()
452 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800453
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.')
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900502 parser.add_argument('--goma_dir', type='path',
503 help='Goma installed directory to mount into the chroot.')
504 parser.add_argument('--goma_client_json', type='path',
505 help='Service account json file to use goma on bot. '
506 'Mounted into the chroot.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400507 parser.add_argument('commands', nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500508
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700509 # SDK overlay tarball options (mutually exclusive).
510 group = parser.add_mutually_exclusive_group()
511 group.add_argument('--toolchains',
512 help=('Comma-separated list of toolchains we expect to be '
513 'using on the chroot. Used for downloading a '
514 'corresponding SDK toolchains group (if one is '
515 'found), which may speed up chroot initialization '
516 'when building for the first time. Otherwise this '
517 'has no effect and will not restrict the chroot in '
518 'any way. Ignored if using --bootstrap.'))
519 group.add_argument('--board',
520 help=('The board we intend to be building in the chroot. '
521 'Used for deriving the list of required toolchains '
522 '(see --toolchains).'))
523
Mike Frysinger34db8692013-11-11 14:54:08 -0500524 # Commands.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400525 group = parser.add_argument_group('Commands')
526 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500527 '--enter', action='store_true', default=False,
528 help='Enter the SDK chroot. Implies --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400529 group.add_argument(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500530 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500531 help='Create the chroot only if it does not already exist. '
532 'Implies --download.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400533 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500534 '--bootstrap', action='store_true', default=False,
535 help='Build everything from scratch, including the sdk. '
536 'Use this only if you need to validate a change '
537 'that affects SDK creation itself (toolchain and '
538 'build are typically the only folk who need this). '
539 'Note this will quite heavily slow down the build. '
540 'This option implies --create --nousepkg.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400541 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500542 '-r', '--replace', action='store_true', default=False,
543 help='Replace an existing SDK chroot. Basically an alias '
544 'for --delete --create.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400545 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500546 '--delete', action='store_true', default=False,
547 help='Delete the current SDK chroot if it exists.')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400548 group.add_argument(
Mike Frysinger34db8692013-11-11 14:54:08 -0500549 '--download', action='store_true', default=False,
550 help='Download the sdk.')
551 commands = group
552
Mike Frysinger80dfce92014-04-21 10:58:53 -0400553 # Namespace options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400554 group = parser.add_argument_group('Namespaces')
555 group.add_argument('--proxy-sim', action='store_true', default=False,
556 help='Simulate a restrictive network requiring an outbound'
557 ' proxy.')
558 group.add_argument('--no-ns-pid', dest='ns_pid',
559 default=True, action='store_false',
560 help='Do not create a new PID namespace.')
Mike Frysinger80dfce92014-04-21 10:58:53 -0400561
Mike Frysinger34db8692013-11-11 14:54:08 -0500562 # Internal options.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400563 group = parser.add_argument_group(
Mike Frysinger34db8692013-11-11 14:54:08 -0500564 'Internal Chromium OS Build Team Options',
565 'Caution: these are for meant for the Chromium OS build team only')
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400566 group.add_argument('--buildbot-log-version', default=False,
567 action='store_true',
568 help='Log SDK version for buildbot consumption')
Mike Frysinger34db8692013-11-11 14:54:08 -0500569
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400570 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500571
572
573def main(argv):
574 conf = cros_build_lib.LoadKeyValueFile(
575 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
576 ignore_missing=True)
577 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
578 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
579 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400580 options = parser.parse_args(argv)
581 chroot_command = options.commands
Brian Harringb938c782012-02-29 15:14:38 -0800582
583 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500584 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800585
Brian Harring1790ac42012-09-23 08:53:33 -0700586 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700587 if host != 'x86_64':
588 parser.error(
589 "cros_sdk is currently only supported on x86_64; you're running"
590 " %s. Please find a x86_64 machine." % (host,))
591
Josh Triplett472a4182013-03-08 11:48:57 -0800592 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
593 if options.proxy_sim:
594 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800595
David James471532c2013-01-21 10:23:31 -0800596 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400597 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400598 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400599 else:
600 first_pid = None
David James471532c2013-01-21 10:23:31 -0800601
Brian Harring218e13c2012-10-10 16:21:26 -0700602 # Expand out the aliases...
603 if options.replace:
604 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800605
Brian Harring218e13c2012-10-10 16:21:26 -0700606 if options.bootstrap:
607 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800608
Brian Harring218e13c2012-10-10 16:21:26 -0700609 # If a command is not given, default to enter.
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400610 # pylint: disable=protected-access
611 # This _group_actions access sucks, but upstream decided to not include an
612 # alternative to optparse's option_list, and this is what they recommend.
Brian Harring218e13c2012-10-10 16:21:26 -0700613 options.enter |= not any(getattr(options, x.dest)
Mike Frysinger2f95cfc2015-06-04 04:00:26 -0400614 for x in commands._group_actions)
615 # pylint: enable=protected-access
Brian Harring218e13c2012-10-10 16:21:26 -0700616 options.enter |= bool(chroot_command)
617
618 if options.enter and options.delete and not options.create:
619 parser.error("Trying to enter the chroot when --delete "
620 "was specified makes no sense.")
621
622 # Finally, discern if we need to create the chroot.
623 chroot_exists = os.path.exists(options.chroot)
624 if options.create or options.enter:
625 # Only create if it's being wiped, or if it doesn't exist.
626 if not options.delete and chroot_exists:
627 options.create = False
628 else:
629 options.download = True
630
631 # Finally, flip create if necessary.
632 if options.enter:
633 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800634
Brian Harringb938c782012-02-29 15:14:38 -0800635 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700636 sdk_version = (bootstrap_latest_version if options.bootstrap
637 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800638 else:
639 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500640 if options.buildbot_log_version:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700641 logging.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800642
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700643 # Based on selections, determine the tarball to fetch.
Brian Harring1790ac42012-09-23 08:53:33 -0700644 if options.sdk_url:
645 urls = [options.sdk_url]
646 elif options.bootstrap:
647 urls = GetStage3Urls(sdk_version)
648 else:
649 urls = GetArchStageTarballs(sdk_version)
650
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700651 # Get URLs for the toolchains overlay, if one is to be used.
652 toolchains_overlay_urls = None
653 if not options.bootstrap:
654 toolchains = None
655 if options.toolchains:
656 toolchains = options.toolchains.split(',')
657 elif options.board:
658 toolchains = toolchain.GetToolchainsForBoard(options.board).keys()
659
660 if toolchains:
661 toolchains_overlay_urls = GetToolchainsOverlayUrls(sdk_version,
662 toolchains)
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700663
Brian Harringb6cf9142012-09-01 20:43:17 -0700664 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700665 lock_path = os.path.join(
666 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400667 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700668 with locking.FileLock(lock_path, 'chroot lock') as lock:
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700669 toolchains_overlay_tarball = None
Brian Harring1790ac42012-09-23 08:53:33 -0700670
Josh Triplett472a4182013-03-08 11:48:57 -0800671 if options.proxy_sim:
672 _ProxySimSetup(options)
673
David James56e6c2c2012-10-24 23:54:41 -0700674 if options.delete and os.path.exists(options.chroot):
675 lock.write_lock()
676 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800677
David James56e6c2c2012-10-24 23:54:41 -0700678 sdk_cache = os.path.join(options.cache_dir, 'sdks')
679 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700680 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700681
David James56e6c2c2012-10-24 23:54:41 -0700682 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500683 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700684 if not os.path.exists(src):
Prathmesh Prabhu06a50562016-10-22 01:41:44 -0700685 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -0700686 continue
687 lock.write_lock(
688 "Upgrade to %r needed but chroot is locked; please exit "
689 "all instances so this upgrade can finish." % src)
690 if not os.path.exists(src):
691 # Note that while waiting for the write lock, src may've vanished;
692 # it's a rare race during the upgrade process that's a byproduct
693 # of us avoiding taking a write lock to do the src check. If we
694 # took a write lock for that check, it would effectively limit
695 # all cros_sdk for a chroot to a single instance.
Prathmesh Prabhu06a50562016-10-22 01:41:44 -0700696 osutils.SafeMakedirsNonRoot(target)
David James56e6c2c2012-10-24 23:54:41 -0700697 elif not os.path.exists(target):
698 # Upgrade occurred, but a reversion, or something whacky
699 # occurred writing to the old location. Wipe and continue.
700 os.rename(src, target)
701 else:
702 # Upgrade occurred once already, but either a reversion or
703 # some before/after separate cros_sdk usage is at play.
704 # Wipe and continue.
705 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700706
David James56e6c2c2012-10-24 23:54:41 -0700707 if options.download:
708 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700709 sdk_tarball = FetchRemoteTarballs(
710 sdk_cache, urls, 'stage3' if options.bootstrap else 'SDK')
711 if toolchains_overlay_urls:
712 toolchains_overlay_tarball = FetchRemoteTarballs(
713 sdk_cache, toolchains_overlay_urls, 'SDK toolchains overlay',
714 allow_none=True)
Brian Harring218e13c2012-10-10 16:21:26 -0700715
David James56e6c2c2012-10-24 23:54:41 -0700716 if options.create:
717 lock.write_lock()
Gilad Arnold6a8f0452015-06-04 11:25:18 -0700718 CreateChroot(options.chroot, sdk_tarball, toolchains_overlay_tarball,
Gilad Arnoldecc86fa2015-05-22 12:06:04 -0700719 options.cache_dir,
David Pursell3528e8e2015-07-07 11:22:15 -0700720 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700721
David James56e6c2c2012-10-24 23:54:41 -0700722 if options.enter:
723 lock.read_lock()
724 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700725 options.chrome_root_mount, options.workspace,
Hidehiko Abeb5daf2f2017-03-02 17:57:43 +0900726 options.goma_dir, options.goma_client_json,
Don Garrett230d1b22015-03-09 16:21:19 -0700727 chroot_command)