blob: eeb1647c66fe91f8cb5dca82b191e11d69019d34 [file] [log] [blame]
Mike Frysinger2de7f042012-07-10 04:45:03 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysinger80dfce92014-04-21 10:58:53 -04005"""This script fetches and prepares an SDK chroot."""
Brian Harringb938c782012-02-29 15:14:38 -08006
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Josh Triplett472a4182013-03-08 11:48:57 -08009import glob
Brian Harringb938c782012-02-29 15:14:38 -080010import os
Josh Triplett472a4182013-03-08 11:48:57 -080011import pwd
David James56e6c2c2012-10-24 23:54:41 -070012import sys
Brian Harringb938c782012-02-29 15:14:38 -080013import urlparse
14
Don Garrett88b8d782014-05-13 17:30:55 -070015from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080016from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070017from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080018from chromite.lib import cros_build_lib
Ralph Nathan59900422015-03-24 10:41:17 -070019from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080020from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080021from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070022from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040023from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080024from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050025from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080026
27cros_build_lib.STRICT_SUDO = True
28
29
Zdenek Behanaa52cea2012-05-30 01:31:11 +020030COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020031
Brian Harringb938c782012-02-29 15:14:38 -080032# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050033MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
34 'src/scripts/sdk_lib/make_chroot.sh')]
35ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
36 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080037
Josh Triplett472a4182013-03-08 11:48:57 -080038# Proxy simulator configuration.
39PROXY_HOST_IP = '192.168.240.1'
40PROXY_PORT = 8080
41PROXY_GUEST_IP = '192.168.240.2'
42PROXY_NETMASK = 30
43PROXY_VETH_PREFIX = 'veth'
44PROXY_CONNECT_PORTS = (80, 443, 9418)
45PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
46PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
47PROXY_APACHE_FALLBACK_PATH = ':'.join(
48 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
49)
50PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
51
Josh Triplett9a495f62013-03-15 18:06:55 -070052# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080053NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080054
Josh Triplett472a4182013-03-08 11:48:57 -080055# Tools needed for --proxy-sim only.
56PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080057
Mike Frysingercc838832014-05-24 13:10:30 -040058
Brian Harring1790ac42012-09-23 08:53:33 -070059def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080060 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070061 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050062 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
63 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070064 for compressor in COMPRESSION_PREFERENCE]
65
66
67def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050068 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070069 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080070
71
Brian Harringae0a5322012-09-15 01:46:51 -070072def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050073 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020074
75 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050076 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020077 urls: List of URLs to try to download. Download will stop on first success.
78
79 Returns:
80 Full path to the downloaded file
81 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020082
Brian Harring1790ac42012-09-23 08:53:33 -070083 # Note we track content length ourselves since certain versions of curl
84 # fail if asked to resume a complete file.
85 # pylint: disable=C0301,W0631
86 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020087 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070088 # http://www.logilab.org/ticket/8766
89 # pylint: disable=E1101
90 parsed = urlparse.urlparse(url)
91 tarball_name = os.path.basename(parsed.path)
92 if parsed.scheme in ('', 'file'):
93 if os.path.exists(parsed.path):
94 return parsed.path
95 continue
96 content_length = 0
Mike Frysinger383367e2014-09-16 15:06:17 -040097 print('Attempting download: %s' % url)
David Jamesc93e6a4d2014-01-13 11:37:36 -080098 result = retry_util.RunCurl(
Mike Frysingerd6e2df02014-11-26 02:55:04 -050099 ['-I', url], redirect_stdout=True, redirect_stderr=True,
100 print_cmd=False)
Brian Harring1790ac42012-09-23 08:53:33 -0700101 successful = False
102 for header in result.output.splitlines():
103 # We must walk the output to find the string '200 OK' for use cases where
104 # a proxy is involved and may have pushed down the actual header.
105 if header.find('200 OK') != -1:
106 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500107 elif header.lower().startswith('content-length:'):
108 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700109 if successful:
110 break
111 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112 break
113 else:
114 raise Exception('No valid URLs found!')
115
Brian Harringae0a5322012-09-15 01:46:51 -0700116 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700117 current_size = 0
118 if os.path.exists(tarball_dest):
119 current_size = os.path.getsize(tarball_dest)
120 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700121 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700122 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100123
Brian Harring1790ac42012-09-23 08:53:33 -0700124 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800125 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700126 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
127 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800128
Brian Harring1790ac42012-09-23 08:53:33 -0700129 # Cleanup old tarballs now since we've successfull fetched; only cleanup
130 # the tarballs for our prefix, or unknown ones.
131 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
132 else 'cros-sdk-')
133 for filename in os.listdir(storage_dir):
134 if filename == tarball_name or filename.startswith(ignored_prefix):
135 continue
Brian Harringb938c782012-02-29 15:14:38 -0800136
Mike Frysinger383367e2014-09-16 15:06:17 -0400137 print('Cleaning up old tarball: %s' % (filename,))
David James56e6c2c2012-10-24 23:54:41 -0700138 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200139
Brian Harringb938c782012-02-29 15:14:38 -0800140 return tarball_dest
141
142
Brian Harring1790ac42012-09-23 08:53:33 -0700143def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800144 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800145
Brian Harring1790ac42012-09-23 08:53:33 -0700146 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700147 '--chroot', chroot_path,
148 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400149 if nousepkg:
150 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800151
152 try:
153 cros_build_lib.RunCommand(cmd, print_cmd=False)
154 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700155 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800156
157
158def DeleteChroot(chroot_path):
159 """Deletes an existing chroot"""
160 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
161 '--delete']
162 try:
163 cros_build_lib.RunCommand(cmd, print_cmd=False)
164 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700165 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800166
167
Brian Harringae0a5322012-09-15 01:46:51 -0700168def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700169 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800170 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400171 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
172 # The os.ST_NOSUID constant wasn't added until python-3.2.
173 if st.f_flag & 0x2:
174 cros_build_lib.Die('chroot cannot be in a nosuid mount')
175
Brian Harringae0a5322012-09-15 01:46:51 -0700176 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800177 if chrome_root:
178 cmd.extend(['--chrome_root', chrome_root])
179 if chrome_root_mount:
180 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700181 if workspace:
182 cmd.extend(['--workspace_root', workspace])
183
Brian Harringb938c782012-02-29 15:14:38 -0800184 if len(additional_args) > 0:
185 cmd.append('--')
186 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700187
Ralph Nathan549d3502015-03-26 17:38:42 -0700188 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
189 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700190 # If we were in interactive mode, ignore the exit code; it'll be whatever
191 # they last ran w/in the chroot and won't matter to us one way or another.
192 # Note this does allow chroot entrance to fail and be ignored during
193 # interactive; this is however a rare case and the user will immediately
194 # see it (nor will they be checking the exit code manually).
195 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700196 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800197
198
David James56e6c2c2012-10-24 23:54:41 -0700199def _SudoCommand():
200 """Get the 'sudo' command, along with all needed environment variables."""
201
David James5a73b4d2013-03-07 10:23:40 -0800202 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
203 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700204 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800205 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700206 value = os.environ.get(key)
207 if value is not None:
208 cmd += ['%s=%s' % (key, value)]
209
210 # Pass in the path to the depot_tools so that users can access them from
211 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400212 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500213
David James56e6c2c2012-10-24 23:54:41 -0700214 return cmd
215
216
Josh Triplett472a4182013-03-08 11:48:57 -0800217def _ReportMissing(missing):
218 """Report missing utilities, then exit.
219
220 Args:
221 missing: List of missing utilities, as returned by
222 osutils.FindMissingBinaries. If non-empty, will not return.
223 """
224
225 if missing:
226 raise SystemExit(
227 'The tool(s) %s were not found.\n'
228 'Please install the appropriate package in your host.\n'
229 'Example(ubuntu):\n'
230 ' sudo apt-get install <packagename>'
231 % ', '.join(missing))
232
233
234def _ProxySimSetup(options):
235 """Set up proxy simulator, and return only in the child environment.
236
237 TODO: Ideally, this should support multiple concurrent invocations of
238 cros_sdk --proxy-sim; currently, such invocations will conflict with each
239 other due to the veth device names and IP addresses. Either this code would
240 need to generate fresh, unused names for all of these before forking, or it
241 would need to support multiple concurrent cros_sdk invocations sharing one
242 proxy and allowing it to exit when unused (without counting on any local
243 service-management infrastructure on the host).
244 """
245
246 may_need_mpm = False
247 apache_bin = osutils.Which('apache2')
248 if apache_bin is None:
249 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
250 if apache_bin is None:
251 _ReportMissing(('apache2',))
252 else:
253 may_need_mpm = True
254
255 # Module names and .so names included for ease of grepping.
256 apache_modules = [('proxy_module', 'mod_proxy.so'),
257 ('proxy_connect_module', 'mod_proxy_connect.so'),
258 ('proxy_http_module', 'mod_proxy_http.so'),
259 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
260
261 # Find the apache module directory, and make sure it has the modules we need.
262 module_dirs = {}
263 for g in PROXY_APACHE_MODULE_GLOBS:
264 for mod, so in apache_modules:
265 for f in glob.glob(os.path.join(g, so)):
266 module_dirs.setdefault(os.path.dirname(f), []).append(so)
267 for apache_module_path, modules_found in module_dirs.iteritems():
268 if len(modules_found) == len(apache_modules):
269 break
270 else:
271 # Appease cros lint, which doesn't understand that this else block will not
272 # fall through to the subsequent code which relies on apache_module_path.
273 apache_module_path = None
274 raise SystemExit(
275 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500276 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800277
278 def check_add_module(name):
279 so = 'mod_%s.so' % name
280 if os.access(os.path.join(apache_module_path, so), os.F_OK):
281 mod = '%s_module' % name
282 apache_modules.append((mod, so))
283 return True
284 return False
285
286 check_add_module('authz_core')
287 if may_need_mpm:
288 for mpm in PROXY_APACHE_MPMS:
289 if check_add_module('mpm_%s' % mpm):
290 break
291
292 veth_host = '%s-host' % PROXY_VETH_PREFIX
293 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
294
295 # Set up pipes from parent to child and vice versa.
296 # The child writes a byte to the parent after calling unshare, so that the
297 # parent can then assign the guest end of the veth interface to the child's
298 # new network namespace. The parent then writes a byte to the child after
299 # assigning the guest interface, so that the child can then configure that
300 # interface. In both cases, if we get back an EOF when reading from the
301 # pipe, we assume the other end exited with an error message, so just exit.
302 parent_readfd, child_writefd = os.pipe()
303 child_readfd, parent_writefd = os.pipe()
304 SUCCESS_FLAG = '+'
305
306 pid = os.fork()
307 if not pid:
308 os.close(parent_readfd)
309 os.close(parent_writefd)
310
311 namespaces.Unshare(namespaces.CLONE_NEWNET)
312 os.write(child_writefd, SUCCESS_FLAG)
313 os.close(child_writefd)
314 if os.read(child_readfd, 1) != SUCCESS_FLAG:
315 # Parent failed; it will have already have outputted an error message.
316 sys.exit(1)
317 os.close(child_readfd)
318
319 # Set up child side of the network.
320 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500321 ('ip', 'link', 'set', 'up', 'lo'),
322 ('ip', 'address', 'add',
323 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
324 'dev', veth_guest),
325 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800326 )
327 try:
328 for cmd in commands:
329 cros_build_lib.RunCommand(cmd, print_cmd=False)
330 except cros_build_lib.RunCommandError:
331 raise SystemExit('Running %r failed!' % (cmd,))
332
333 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
334 for proto in ('http', 'https', 'ftp'):
335 os.environ[proto + '_proxy'] = proxy_url
336 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
337 os.environ.pop(v, None)
338 return
339
340 os.close(child_readfd)
341 os.close(child_writefd)
342
343 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
344 # Child failed; it will have already have outputted an error message.
345 sys.exit(1)
346 os.close(parent_readfd)
347
348 # Set up parent side of the network.
349 uid = int(os.environ.get('SUDO_UID', '0'))
350 gid = int(os.environ.get('SUDO_GID', '0'))
351 if uid == 0 or gid == 0:
352 for username in PROXY_APACHE_FALLBACK_USERS:
353 try:
354 pwnam = pwd.getpwnam(username)
355 uid, gid = pwnam.pw_uid, pwnam.pw_gid
356 break
357 except KeyError:
358 continue
359 if uid == 0 or gid == 0:
360 raise SystemExit('Could not find a non-root user to run Apache as')
361
362 chroot_parent, chroot_base = os.path.split(options.chroot)
363 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
364 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
365
366 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500367 'User #%u' % uid,
368 'Group #%u' % gid,
369 'PidFile %s' % pid_file,
370 'ErrorLog %s' % log_file,
371 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
372 'ServerName %s' % PROXY_HOST_IP,
373 'ProxyRequests On',
374 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800375 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500376 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
377 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800378 ]
379 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500380 ('ip', 'link', 'add', 'name', veth_host,
381 'type', 'veth', 'peer', 'name', veth_guest),
382 ('ip', 'address', 'add',
383 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
384 'dev', veth_host),
385 ('ip', 'link', 'set', veth_host, 'up'),
386 ([apache_bin, '-f', '/dev/null'] +
387 [arg for d in apache_directives for arg in ('-C', d)]),
388 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800389 )
390 cmd = None # Make cros lint happy.
391 try:
392 for cmd in commands:
393 cros_build_lib.RunCommand(cmd, print_cmd=False)
394 except cros_build_lib.RunCommandError:
395 # Clean up existing interfaces, if any.
396 cmd_cleanup = ('ip', 'link', 'del', veth_host)
397 try:
398 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
399 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700400 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800401 raise SystemExit('Running %r failed!' % (cmd,))
402 os.write(parent_writefd, SUCCESS_FLAG)
403 os.close(parent_writefd)
404
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400405 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800406
407
Mike Frysingera78a56e2012-11-20 06:02:30 -0500408def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700409 """Re-execute cros_sdk as root.
410
411 Also unshare the mount namespace so as to ensure that processes outside
412 the chroot can't mess with our mounts.
413 """
414 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500415 cmd = _SudoCommand() + ['--'] + argv
416 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500417 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400418 # We must set up the cgroups mounts before we enter our own namespace.
419 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800420 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500421 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700422
423
Mike Frysinger34db8692013-11-11 14:54:08 -0500424def _CreateParser(sdk_latest_version, bootstrap_latest_version):
425 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700426 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800427
Brian Harring218e13c2012-10-10 16:21:26 -0700428This script is used for manipulating local chroot environments; creating,
429deleting, downloading, etc. If given --enter (or no args), it defaults
430to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800431
Brian Harring218e13c2012-10-10 16:21:26 -0700432If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700433
Brian Harring218e13c2012-10-10 16:21:26 -0700434 parser = commandline.OptionParser(usage=usage, caching=True)
435
Mike Frysinger34db8692013-11-11 14:54:08 -0500436 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500437 default_chroot = os.path.join(constants.SOURCE_ROOT,
438 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700439 parser.add_option(
440 '--chroot', dest='chroot', default=default_chroot, type='path',
441 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800442
Brian Harring218e13c2012-10-10 16:21:26 -0700443 parser.add_option('--chrome_root', default=None, type='path',
444 help='Mount this chrome root into the SDK chroot')
445 parser.add_option('--chrome_root_mount', default=None, type='path',
446 help='Mount chrome into this path inside SDK chroot')
447 parser.add_option('--nousepkg', action='store_true', default=False,
448 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800449 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700450 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800451 help=('''Use sdk tarball located at this url.
452 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700453 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500454 help=('Use this sdk version. For prebuilt, current is %r'
455 ', for bootstrapping it is %r.'
456 % (sdk_latest_version, bootstrap_latest_version)))
Don Garrett230d1b22015-03-09 16:21:19 -0700457 parser.add_option('--workspace', default=None,
458 help='Workspace directory to mount into the chroot.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500459
460 # Commands.
461 group = parser.add_option_group('Commands')
462 group.add_option(
463 '--enter', action='store_true', default=False,
464 help='Enter the SDK chroot. Implies --create.')
465 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500466 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500467 help='Create the chroot only if it does not already exist. '
468 'Implies --download.')
469 group.add_option(
470 '--bootstrap', action='store_true', default=False,
471 help='Build everything from scratch, including the sdk. '
472 'Use this only if you need to validate a change '
473 'that affects SDK creation itself (toolchain and '
474 'build are typically the only folk who need this). '
475 'Note this will quite heavily slow down the build. '
476 'This option implies --create --nousepkg.')
477 group.add_option(
478 '-r', '--replace', action='store_true', default=False,
479 help='Replace an existing SDK chroot. Basically an alias '
480 'for --delete --create.')
481 group.add_option(
482 '--delete', action='store_true', default=False,
483 help='Delete the current SDK chroot if it exists.')
484 group.add_option(
485 '--download', action='store_true', default=False,
486 help='Download the sdk.')
487 commands = group
488
Mike Frysinger80dfce92014-04-21 10:58:53 -0400489 # Namespace options.
490 group = parser.add_option_group('Namespaces')
491 group.add_option('--proxy-sim', action='store_true', default=False,
492 help='Simulate a restrictive network requiring an outbound'
493 ' proxy.')
494 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger41f28032014-09-29 16:53:41 -0500495 default=True, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400496 help='Do not create a new PID namespace.')
497
Mike Frysinger34db8692013-11-11 14:54:08 -0500498 # Internal options.
499 group = parser.add_option_group(
500 'Internal Chromium OS Build Team Options',
501 'Caution: these are for meant for the Chromium OS build team only')
502 group.add_option('--buildbot-log-version', default=False, action='store_true',
503 help='Log SDK version for buildbot consumption')
504
505 return (parser, commands)
506
507
508def main(argv):
509 conf = cros_build_lib.LoadKeyValueFile(
510 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
511 ignore_missing=True)
512 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
513 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
514 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700515 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800516
517 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500518 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800519
Brian Harring1790ac42012-09-23 08:53:33 -0700520 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700521 if host != 'x86_64':
522 parser.error(
523 "cros_sdk is currently only supported on x86_64; you're running"
524 " %s. Please find a x86_64 machine." % (host,))
525
Josh Triplett472a4182013-03-08 11:48:57 -0800526 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
527 if options.proxy_sim:
528 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800529
David James471532c2013-01-21 10:23:31 -0800530 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400531 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400532 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400533 else:
534 first_pid = None
David James471532c2013-01-21 10:23:31 -0800535
Brian Harring218e13c2012-10-10 16:21:26 -0700536 # Expand out the aliases...
537 if options.replace:
538 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800539
Brian Harring218e13c2012-10-10 16:21:26 -0700540 if options.bootstrap:
541 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800542
Brian Harring218e13c2012-10-10 16:21:26 -0700543 # If a command is not given, default to enter.
544 options.enter |= not any(getattr(options, x.dest)
545 for x in commands.option_list)
546 options.enter |= bool(chroot_command)
547
548 if options.enter and options.delete and not options.create:
549 parser.error("Trying to enter the chroot when --delete "
550 "was specified makes no sense.")
551
552 # Finally, discern if we need to create the chroot.
553 chroot_exists = os.path.exists(options.chroot)
554 if options.create or options.enter:
555 # Only create if it's being wiped, or if it doesn't exist.
556 if not options.delete and chroot_exists:
557 options.create = False
558 else:
559 options.download = True
560
561 # Finally, flip create if necessary.
562 if options.enter:
563 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800564
Brian Harringb938c782012-02-29 15:14:38 -0800565 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700566 sdk_version = (bootstrap_latest_version if options.bootstrap
567 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800568 else:
569 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500570 if options.buildbot_log_version:
571 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800572
Brian Harring1790ac42012-09-23 08:53:33 -0700573 # Based on selections, fetch the tarball.
574 if options.sdk_url:
575 urls = [options.sdk_url]
576 elif options.bootstrap:
577 urls = GetStage3Urls(sdk_version)
578 else:
579 urls = GetArchStageTarballs(sdk_version)
580
Brian Harringb6cf9142012-09-01 20:43:17 -0700581 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700582 lock_path = os.path.join(
583 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400584 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700585 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700586
Josh Triplett472a4182013-03-08 11:48:57 -0800587 if options.proxy_sim:
588 _ProxySimSetup(options)
589
David James56e6c2c2012-10-24 23:54:41 -0700590 if options.delete and os.path.exists(options.chroot):
591 lock.write_lock()
592 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800593
David James56e6c2c2012-10-24 23:54:41 -0700594 sdk_cache = os.path.join(options.cache_dir, 'sdks')
595 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700596 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700597
David James56e6c2c2012-10-24 23:54:41 -0700598 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500599 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700600 if not os.path.exists(src):
601 osutils.SafeMakedirs(target)
602 continue
603 lock.write_lock(
604 "Upgrade to %r needed but chroot is locked; please exit "
605 "all instances so this upgrade can finish." % src)
606 if not os.path.exists(src):
607 # Note that while waiting for the write lock, src may've vanished;
608 # it's a rare race during the upgrade process that's a byproduct
609 # of us avoiding taking a write lock to do the src check. If we
610 # took a write lock for that check, it would effectively limit
611 # all cros_sdk for a chroot to a single instance.
612 osutils.SafeMakedirs(target)
613 elif not os.path.exists(target):
614 # Upgrade occurred, but a reversion, or something whacky
615 # occurred writing to the old location. Wipe and continue.
616 os.rename(src, target)
617 else:
618 # Upgrade occurred once already, but either a reversion or
619 # some before/after separate cros_sdk usage is at play.
620 # Wipe and continue.
621 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700622
David James56e6c2c2012-10-24 23:54:41 -0700623 if options.download:
624 lock.write_lock()
625 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700626
David James56e6c2c2012-10-24 23:54:41 -0700627 if options.create:
628 lock.write_lock()
629 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
630 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700631
David James56e6c2c2012-10-24 23:54:41 -0700632 if options.enter:
633 lock.read_lock()
634 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700635 options.chrome_root_mount, options.workspace,
636 chroot_command)